diff --git a/.tx/config b/.tx/config index aea7ffdd5d..285c857ba6 100644 --- a/.tx/config +++ b/.tx/config @@ -1,179 +1,178 @@ [main] host = https://www.transifex.com -[orcid-angular.src-locale-properties-environment-banner-environment-banner-en-properties--transifex] -file_filter = src/locale/properties/environment-banner/environment-banner..properties +[o:orcid-inc-1:p:orcid-angular:r:environment-banner] +file_filter = src/locale/properties/environment-banner/environment-banner..properties +source_file = src/locale/properties/environment-banner/environment-banner.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/environment-banner/environment-banner.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-home-home-en-properties--transifex] -file_filter = src/locale/properties/home/home..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-account-account-en-properties--transifex] +file_filter = src/locale/properties/account/account..properties +source_file = src/locale/properties/account/account.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/home/home.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-layout-layout-en-properties--transifex] -file_filter = src/locale/properties/layout/layout..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-authorize-authorize-en-properties--transifex] +file_filter = src/locale/properties/authorize/authorize..properties +source_file = src/locale/properties/authorize/authorize.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/layout/layout.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-material-material-en-properties--transifex] -file_filter = src/locale/properties/material/material..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-delegators-delegators-en-properties--transifex] +file_filter = src/locale/properties/delegators/delegators..properties +source_file = src/locale/properties/delegators/delegators.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/material/material.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-password-recovery-password-recovery-en-properties--transifex] -file_filter = src/locale/properties/password-recovery/password-recovery..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-funding-funding-en-properties--transifex] +file_filter = src/locale/properties/funding/funding..properties +source_file = src/locale/properties/funding/funding.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/password-recovery/password-recovery.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-profile-profile-en-properties--transifex] -file_filter = src/locale/properties/profile/profile..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-home-home-en-properties--transifex] +file_filter = src/locale/properties/home/home..properties +source_file = src/locale/properties/home/home.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/profile/profile.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-search-search-en-properties--transifex] -file_filter = src/locale/properties/search/search..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-inbox-inbox-en-properties--transifex] +file_filter = src/locale/properties/inbox/inbox..properties +source_file = src/locale/properties/inbox/inbox.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/search/search.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-signin-signin-en-properties--transifex] -file_filter = src/locale/properties/signin/signin..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-institutional-institutional-en-properties--transifex] +file_filter = src/locale/properties/institutional/institutional..properties +source_file = src/locale/properties/institutional/institutional.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/signin/signin.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-institutional-institutional-en-properties--transifex] -file_filter = src/locale/properties/institutional/institutional..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-layout-layout-en-properties--transifex] +file_filter = src/locale/properties/layout/layout..properties +source_file = src/locale/properties/layout/layout.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/institutional/institutional.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-linking-linking-en-properties--transifex] -file_filter = src/locale/properties/linking/linking..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-linking-linking-en-properties--transifex] +file_filter = src/locale/properties/linking/linking..properties +source_file = src/locale/properties/linking/linking.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/linking/linking.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-register-register-en-properties--transifex] -file_filter = src/locale/properties/register/register..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-material-material-en-properties--transifex] +file_filter = src/locale/properties/material/material..properties +source_file = src/locale/properties/material/material.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/register/register.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-inbox-inbox-en-properties--transifex] -file_filter = src/locale/properties/inbox/inbox..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-my-orcid-alerts-my-orcid-alerts-en-properties--transifex] +file_filter = src/locale/properties/my-orcid-alerts/my-orcid-alerts..properties +source_file = src/locale/properties/my-orcid-alerts/my-orcid-alerts.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/inbox/inbox.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-delegators-delegators-en-properties--transifex] -file_filter = src/locale/properties/delegators/delegators..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-password-recovery-password-recovery-en-properties--transifex] +file_filter = src/locale/properties/password-recovery/password-recovery..properties +source_file = src/locale/properties/password-recovery/password-recovery.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/delegators/delegators.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-authorize-authorize-en-properties--transifex] -file_filter = src/locale/properties/authorize/authorize..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-peer-review-peer-review-en-properties--transifex] +file_filter = src/locale/properties/peer-review/peer-review..properties +source_file = src/locale/properties/peer-review/peer-review.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/authorize/authorize.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-shared-shared-en-properties--transifex] -file_filter = src/locale/properties/shared/shared..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-profile-profile-en-properties--transifex] +file_filter = src/locale/properties/profile/profile..properties +source_file = src/locale/properties/profile/profile.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/shared/shared.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-two-factor-two-factor-en-properties--transifex] -file_filter = src/locale/properties/two-factor/two-factor..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-record-record-en-properties--transifex] +file_filter = src/locale/properties/record/record..properties +source_file = src/locale/properties/record/record.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/two-factor/two-factor.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-record-record-en-properties--transifex] -file_filter = src/locale/properties/record/record..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-register-register-en-properties--transifex] +file_filter = src/locale/properties/register/register..properties +source_file = src/locale/properties/register/register.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/record/record.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-top-bar-top-bar-en-properties--transifex] -file_filter = src/locale/properties/top-bar/top-bar..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-research-resources-research-resources-en-properties--transifex] +file_filter = src/locale/properties/research-resources/research-resources..properties +source_file = src/locale/properties/research-resources/research-resources.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/top-bar/top-bar.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-peer-review-peer-review-en-properties--transifex] -file_filter = src/locale/properties/peer-review/peer-review..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-search-search-en-properties--transifex] +file_filter = src/locale/properties/search/search..properties +source_file = src/locale/properties/search/search.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/peer-review/peer-review.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-my-orcid-alerts-my-orcid-alerts-en-properties--transifex] -file_filter = src/locale/properties/my-orcid-alerts/my-orcid-alerts..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-shared-shared-en-properties--transifex] +file_filter = src/locale/properties/shared/shared..properties +source_file = src/locale/properties/shared/shared.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/my-orcid-alerts/my-orcid-alerts.en.properties -source_lang = en -type = UNICODEPROPERTIES - -[orcid-angular.src-locale-properties-research-resources-research-resources-en-properties--transifex] -file_filter = src/locale/properties/research-resources/research-resources..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-side-bar-side-bar-en-properties--transifex] +file_filter = src/locale/properties/side-bar/side-bar..properties +source_file = src/locale/properties/side-bar/side-bar.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/research-resources/research-resources.en.properties -source_lang = en -type = UNICODEPROPERTIES - -[orcid-angular.src-locale-properties-funding-funding-en-properties--transifex] -file_filter = src/locale/properties/funding/funding..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-signin-signin-en-properties--transifex] +file_filter = src/locale/properties/signin/signin..properties +source_file = src/locale/properties/signin/signin.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/funding/funding.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-side-bar-side-bar-en-properties--transifex] -file_filter = src/locale/properties/side-bar/side-bar..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-top-bar-top-bar-en-properties--transifex] +file_filter = src/locale/properties/top-bar/top-bar..properties +source_file = src/locale/properties/top-bar/top-bar.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/side-bar/side-bar.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-works-works-en-properties--transifex] -file_filter = src/locale/properties/works/works..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-two-factor-two-factor-en-properties--transifex] +file_filter = src/locale/properties/two-factor/two-factor..properties +source_file = src/locale/properties/two-factor/two-factor.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/works/works.en.properties -source_lang = en -type = UNICODEPROPERTIES -[orcid-angular.src-locale-properties-account-account-en-properties--transifex] -file_filter = src/locale/properties/account/account..properties +[o:orcid-inc-1:p:orcid-angular:r:src-locale-properties-works-works-en-properties--transifex] +file_filter = src/locale/properties/works/works..properties +source_file = src/locale/properties/works/works.en.properties +source_lang = en +type = UNICODEPROPERTIES minimum_perc = 0 -source_file = src/locale/properties/account/account.en.properties -source_lang = en -type = UNICODEPROPERTIES + diff --git a/.tx/config_20230220084312.bak b/.tx/config_20230220084312.bak new file mode 100755 index 0000000000..bfadc293fb --- /dev/null +++ b/.tx/config_20230220084312.bak @@ -0,0 +1,179 @@ +[main] +host = https://www.transifex.com + +[orcid-angular.environment-banner] +file_filter = src/locale/properties/environment-banner/environment-banner..properties +minimum_perc = 0 +source_file = src/locale/properties/environment-banner/environment-banner.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-home-home-en-properties--transifex] +file_filter = src/locale/properties/home/home..properties +minimum_perc = 0 +source_file = src/locale/properties/home/home.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-layout-layout-en-properties--transifex] +file_filter = src/locale/properties/layout/layout..properties +minimum_perc = 0 +source_file = src/locale/properties/layout/layout.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-material-material-en-properties--transifex] +file_filter = src/locale/properties/material/material..properties +minimum_perc = 0 +source_file = src/locale/properties/material/material.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-password-recovery-password-recovery-en-properties--transifex] +file_filter = src/locale/properties/password-recovery/password-recovery..properties +minimum_perc = 0 +source_file = src/locale/properties/password-recovery/password-recovery.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-profile-profile-en-properties--transifex] +file_filter = src/locale/properties/profile/profile..properties +minimum_perc = 0 +source_file = src/locale/properties/profile/profile.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-search-search-en-properties--transifex] +file_filter = src/locale/properties/search/search..properties +minimum_perc = 0 +source_file = src/locale/properties/search/search.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-signin-signin-en-properties--transifex] +file_filter = src/locale/properties/signin/signin..properties +minimum_perc = 0 +source_file = src/locale/properties/signin/signin.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-institutional-institutional-en-properties--transifex] +file_filter = src/locale/properties/institutional/institutional..properties +minimum_perc = 0 +source_file = src/locale/properties/institutional/institutional.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-linking-linking-en-properties--transifex] +file_filter = src/locale/properties/linking/linking..properties +minimum_perc = 0 +source_file = src/locale/properties/linking/linking.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-register-register-en-properties--transifex] +file_filter = src/locale/properties/register/register..properties +minimum_perc = 0 +source_file = src/locale/properties/register/register.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-inbox-inbox-en-properties--transifex] +file_filter = src/locale/properties/inbox/inbox..properties +minimum_perc = 0 +source_file = src/locale/properties/inbox/inbox.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-delegators-delegators-en-properties--transifex] +file_filter = src/locale/properties/delegators/delegators..properties +minimum_perc = 0 +source_file = src/locale/properties/delegators/delegators.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-authorize-authorize-en-properties--transifex] +file_filter = src/locale/properties/authorize/authorize..properties +minimum_perc = 0 +source_file = src/locale/properties/authorize/authorize.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-shared-shared-en-properties--transifex] +file_filter = src/locale/properties/shared/shared..properties +minimum_perc = 0 +source_file = src/locale/properties/shared/shared.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-two-factor-two-factor-en-properties--transifex] +file_filter = src/locale/properties/two-factor/two-factor..properties +minimum_perc = 0 +source_file = src/locale/properties/two-factor/two-factor.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-record-record-en-properties--transifex] +file_filter = src/locale/properties/record/record..properties +minimum_perc = 0 +source_file = src/locale/properties/record/record.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-top-bar-top-bar-en-properties--transifex] +file_filter = src/locale/properties/top-bar/top-bar..properties +minimum_perc = 0 +source_file = src/locale/properties/top-bar/top-bar.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-peer-review-peer-review-en-properties--transifex] +file_filter = src/locale/properties/peer-review/peer-review..properties +minimum_perc = 0 +source_file = src/locale/properties/peer-review/peer-review.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-my-orcid-alerts-my-orcid-alerts-en-properties--transifex] +file_filter = src/locale/properties/my-orcid-alerts/my-orcid-alerts..properties +minimum_perc = 0 +source_file = src/locale/properties/my-orcid-alerts/my-orcid-alerts.en.properties +source_lang = en +type = UNICODEPROPERTIES + + +[orcid-angular.src-locale-properties-research-resources-research-resources-en-properties--transifex] +file_filter = src/locale/properties/research-resources/research-resources..properties +minimum_perc = 0 +source_file = src/locale/properties/research-resources/research-resources.en.properties +source_lang = en +type = UNICODEPROPERTIES + + +[orcid-angular.src-locale-properties-funding-funding-en-properties--transifex] +file_filter = src/locale/properties/funding/funding..properties +minimum_perc = 0 +source_file = src/locale/properties/funding/funding.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-side-bar-side-bar-en-properties--transifex] +file_filter = src/locale/properties/side-bar/side-bar..properties +minimum_perc = 0 +source_file = src/locale/properties/side-bar/side-bar.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-works-works-en-properties--transifex] +file_filter = src/locale/properties/works/works..properties +minimum_perc = 0 +source_file = src/locale/properties/works/works.en.properties +source_lang = en +type = UNICODEPROPERTIES + +[orcid-angular.src-locale-properties-account-account-en-properties--transifex] +file_filter = src/locale/properties/account/account..properties +minimum_perc = 0 +source_file = src/locale/properties/account/account.en.properties +source_lang = en +type = UNICODEPROPERTIES diff --git a/.zshrc b/.zshrc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/angular.json b/angular.json index 77dfecebe6..2b9e148105 100644 --- a/angular.json +++ b/angular.json @@ -145,7 +145,7 @@ "buildOptimizer": false, "outputPath": "dist" }, - "local-tomcat": { + "local-with-proxy": { "budgets": [ { "type": "anyComponentStyle", @@ -155,7 +155,7 @@ "fileReplacements": [ { "replace": "src/environments/environment.ts", - "with": "src/environments/environment.local.tomcat.ts" + "with": "src/environments/environment.local-with-proxy.ts" } ] }, @@ -278,6 +278,14 @@ "browserTarget": "ng-orcid:build:local" }, "configurations": { + "local-qa": { + "browserTarget": "ng-orcid:build:local-with-proxy", + "proxyConfig": "src/proxy.conf.qa.mjs" + }, + "local-sandbox": { + "browserTarget": "ng-orcid:build:local-with-proxy", + "proxyConfig": "src/proxy.conf.sandbox..mjs" + }, "local": { "browserTarget": "ng-orcid:build:local" }, diff --git a/cypress/e2e/api/orcid_internal.cy.js b/cypress/e2e/api/orcid_internal.cy.js new file mode 100644 index 0000000000..2d6f4b155d --- /dev/null +++ b/cypress/e2e/api/orcid_internal.cy.js @@ -0,0 +1,26 @@ +/// +import { qase } from 'cypress-qase-reporter/dist/mocha' +import userData from '../../fixtures/testing-users.fixture.json' + +describe('Scope /orcid-internal', async function () { + //test requires VPN connection + + const curlStatement = + "curl -i -L -k -H 'Accept: application/json' --data 'client_id=" + + userData.cyAPI_orcid_internal.clientId + + '&client_secret=' + + userData.cyAPI_orcid_internal.secret + + "&grant_type=client_credentials&redirect_uri=https://qa.orcid.org/&scope=/premium-notification /orcid-internal' http://reg-qa-appall-dfw-x1.int.orcid.org:13103/orcid-internal-api/oauth/token" + + qase( + '150', + it('Verify scope /orcid-internal returns an access token', function () { + cy.exec(curlStatement).then((response) => { + //verify curl was executed successfully + expect(response.code).to.eq(0) + expect(response.stdout).to.contain('HTTP/1.1 200') + expect(response.stdout).to.contain('access_token') + }) + }) + ) //qase +}) diff --git a/cypress/e2e/contributors/API-3.0-GET-work-added-manually-credit-role.cy.js b/cypress/e2e/contributors/API-3.0-GET-work-added-manually-credit-role.cy.js new file mode 100644 index 0000000000..e859a0064d --- /dev/null +++ b/cypress/e2e/contributors/API-3.0-GET-work-added-manually-credit-role.cy.js @@ -0,0 +1,109 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('API 3.0 - GET work added manually', async function () { + const curlReadAllWorks = + "curl -i -H 'Accept: application/json' -H 'Authorization: Bearer " + + userData.cyRecordOwner.clientBearer + + "' -X GET '" + + Cypress.env('membersAPI_URL') + + userData.cyRecordOwner.oid + + Cypress.env('membersAPI_allWorksEndpoint') + + "'" + + const curlReadSingleWork = + "curl -i -H 'Accept: application/json' -H 'Authorization: Bearer " + + userData.cyRecordOwner.clientBearer + + "' -X GET '" + + Cypress.env('membersAPI_URL') + + userData.cyRecordOwner.oid + + Cypress.env('membersAPI_workEndpoint') + + '/' //here append "{PUTCODE}" + "'" + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '35', + it('API 3.0 - GET work added manually - credit role', function () { + let putCode + const workType = 'Book' + const title = 'Cypress test contributors' + const otherContributorName = 'Michael Jordan' + const creditRole = 'Investigation' + + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-manually').click({ force: true }) + cy.get('#cy-work-types').click() + cy.get('#cy-work-types-panel').within(($myOptions) => { + cy.contains(workType).click() + }) + cy.get('#title-input').clear().type(title) + + //add someone else as contributor with credit role + cy.get('.cy-add-another-contributor').click() + cy.get('app-work-contributors').within(($section) => { + cy.get('[formcontrolname="creditName"]') + .clear() + .type(otherContributorName) + cy.get('[formcontrolname="role"]').click({ force: true }) + }) + //choose credit role + cy.get('[role="listbox"]').within(($list) => { + //to do REPLACE with id for the element next sprint + cy.contains(creditRole).click() + }) + + //save entry + cy.get('#save-work-button').wait(1000).click({ force: true }) + cy.wait(2000) + + //Verify work was added + cy.contains('app-panel-data', otherContributorName).within( + ($thisWork) => { + cy.contains('Show more detail').click() + } + ) + + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(otherContributorName) + + //Read works to grab putcode + //There should only be one work + cy.exec(curlReadAllWorks).then((response) => { + //verify curl was executed successfully + expect(response.code).to.eq(0) + //verify http response status is successful: 200 + expect(response.stdout).to.contain('HTTP/2 200') + //grab put code + const responseString = response.stdout + const putcodeIndex = responseString.indexOf('put-code":') + const putCodeStartPosition = putcodeIndex + 10 // +length + const putCodeEndPosition = responseString.indexOf(',"created-date') + putCode = responseString.substring( + putCodeStartPosition, + putCodeEndPosition + ) + cy.log('putCode found:' + putCode) + cy.exec(curlReadSingleWork + putCode + "'").then((singleWorkResp) => { + //verify curl was executed successfully + expect(singleWorkResp.code).to.eq(0) + //verify http response status is successful: 200 + expect(singleWorkResp.stdout).to.contain('HTTP/2 200') + }) + }) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/API-3.0-GET-work-added-manually-no-role.cy.js b/cypress/e2e/contributors/API-3.0-GET-work-added-manually-no-role.cy.js new file mode 100644 index 0000000000..2954b15218 --- /dev/null +++ b/cypress/e2e/contributors/API-3.0-GET-work-added-manually-no-role.cy.js @@ -0,0 +1,102 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('API 3.0 - GET work added manually', async function () { + const curlReadAllWorks = + "curl -i -H 'Accept: application/json' -H 'Authorization: Bearer " + + userData.cyRecordOwner.clientBearer + + "' -X GET '" + + Cypress.env('membersAPI_URL') + + userData.cyRecordOwner.oid + + Cypress.env('membersAPI_allWorksEndpoint') + + "'" + + const curlReadSingleWork = + "curl -i -H 'Accept: application/json' -H 'Authorization: Bearer " + + userData.cyRecordOwner.clientBearer + + "' -X GET '" + + Cypress.env('membersAPI_URL') + + userData.cyRecordOwner.oid + + Cypress.env('membersAPI_workEndpoint') + + '/' //here append "{PUTCODE}" + "'" + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '35', + it('API 3.0 - GET work added manually - no role', function () { + let putCode + const workType = 'Book' + const title = 'Cypress test contributors' + const otherContributorName = 'Michael Jordan' + + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-manually').click({ force: true }) + cy.get('#cy-work-types').click() + cy.get('#cy-work-types-panel').within(($myOptions) => { + cy.contains(workType).click() + }) + cy.get('#title-input').clear().type(title) + + //add someone else as contributor with credit role + cy.get('.cy-add-another-contributor').click() + cy.get('app-work-contributors').within(($section) => { + cy.get('[formcontrolname="creditName"]') + .clear() + .type(otherContributorName) + }) + + //save entry + cy.get('#save-work-button').wait(1000).click({ force: true }) + cy.wait(2000) + + //Verify work was added + cy.contains('app-panel-data', otherContributorName).within( + ($thisWork) => { + cy.contains('Show more detail').click() + } + ) + + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(otherContributorName) + + //Read works to grab putcode + //There should only be one work + cy.exec(curlReadAllWorks).then((response) => { + //verify curl was executed successfully + expect(response.code).to.eq(0) + //verify http response status is successful: 200 + expect(response.stdout).to.contain('HTTP/2 200') + //grab put code + const responseString = response.stdout + const putcodeIndex = responseString.indexOf('put-code":') + const putCodeStartPosition = putcodeIndex + 10 // +length + const putCodeEndPosition = responseString.indexOf(',"created-date') + putCode = responseString.substring( + putCodeStartPosition, + putCodeEndPosition + ) + cy.log('putCode found:' + putCode) + cy.exec(curlReadSingleWork + putCode + "'").then((singleWorkResp) => { + //verify curl was executed successfully + expect(singleWorkResp.code).to.eq(0) + //verify http response status is successful: 200 + expect(singleWorkResp.stdout).to.contain('HTTP/2 200') + }) + }) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/add-DOI-less-50-contrib-WITH-roles-AND-sequence.cy.js b/cypress/e2e/contributors/add-DOI-less-50-contrib-WITH-roles-AND-sequence.cy.js new file mode 100644 index 0000000000..82eda718ab --- /dev/null +++ b/cypress/e2e/contributors/add-DOI-less-50-contrib-WITH-roles-AND-sequence.cy.js @@ -0,0 +1,42 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add DOI with less than 50 contributors WITH roles AND sequences', async function () { + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '41', + it('Add DOI with less than 50 contributors WITH roles AND sequences', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-doi').click({ force: true }) + cy.get('#external-id-input') + .clear() + .type(userData.cyRecordOwner.doi_qase41) + cy.get('[id^=cy-retrieve-work-details]').click() + cy.wait(4000) //need to wait for back end + + //save entry + cy.get('#save-work-button').click({ force: true }) + cy.wait(2000) + + //Verify work was added + cy.get('app-work-stack').should( + 'contain', + userData.cyRecordOwner.doi_qase41 + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/add-DOI-less-50-contrib-WITH-roles-NO-sequence.cy.js b/cypress/e2e/contributors/add-DOI-less-50-contrib-WITH-roles-NO-sequence.cy.js new file mode 100644 index 0000000000..dbca0c23a3 --- /dev/null +++ b/cypress/e2e/contributors/add-DOI-less-50-contrib-WITH-roles-NO-sequence.cy.js @@ -0,0 +1,44 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add DOI with less than 50 contributors WITH roles NO sequences', async function () { + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '42', + it('Add DOI with less than 50 contributors WITH roles NO sequences', function () { + const workType = 'Book' + + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-doi').click({ force: true }) + cy.get('#external-id-input') + .clear() + .type(userData.cyRecordOwner.doi_qase42) + //retrieve data + cy.get('[id^=cy-retrieve-work-details]').click() + cy.wait(4000) + + //save entry + cy.get('#save-work-button').click({ force: true }) + cy.wait(2000) + //Verify work was added + cy.get('app-work-stack').should( + 'contain', + userData.cyRecordOwner.doi_qase42 + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/add-DOI-more-50-contrib-WITH-roles-AND-sequence.cy.js b/cypress/e2e/contributors/add-DOI-more-50-contrib-WITH-roles-AND-sequence.cy.js new file mode 100644 index 0000000000..d9fc866a8f --- /dev/null +++ b/cypress/e2e/contributors/add-DOI-more-50-contrib-WITH-roles-AND-sequence.cy.js @@ -0,0 +1,49 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add DOI with more than 50 contributors WITH roles AND sequences', async function () { + const noticeMessage = 'This work has a large number of contributors' + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '43', + it('Add DOI with more than 50 contributors WITH roles AND sequences', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-doi').click({ force: true }) + cy.get('#external-id-input') + .clear() + .type(userData.cyRecordOwner.doi_qase43) + cy.get('[id^=cy-retrieve-work-details]').click() + cy.wait(4000) //need to wait for back end + + //verify link to add one more contrib is not displayed + cy.contains('Add another contributor').should('not.exist') + + //verify the panel is displayed + cy.contains('.notice-panel', noticeMessage).should('exist') + + //save entry + cy.get('#save-work-button').click({ force: true }) + cy.wait(2000) + + //Verify work was added + cy.get('app-work-stack').contains(userData.cyRecordOwner.doi_qase43, { + matchCase: false, + }) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/add-PUBMED-less-50-contrib.cy.js b/cypress/e2e/contributors/add-PUBMED-less-50-contrib.cy.js new file mode 100644 index 0000000000..72e4383707 --- /dev/null +++ b/cypress/e2e/contributors/add-PUBMED-less-50-contrib.cy.js @@ -0,0 +1,44 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add PUBMED ID with less than 50 contributors', async function () { + const pubmedURL = 'https://pubmed.ncbi.nlm.nih.gov/' + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '44', + it('Add PUBMED ID with less than 50 contributors', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-pubmed').click({ force: true }) + cy.get('#external-id-input') + .clear() + .type(pubmedURL + userData.cyRecordOwner.pubmed_qase44 + '/') + cy.get('[id^=cy-retrieve-work-details]').click() + cy.wait(4000) //need to wait for back end + + //save entry + cy.get('#save-work-button').click({ force: true }) + cy.wait(2000) + + //Verify work was added + cy.get('app-work-stack').should( + 'contain', + userData.cyRecordOwner.pubmed_qase44 + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/add-PUBMED-more-50-contrib.cy.js b/cypress/e2e/contributors/add-PUBMED-more-50-contrib.cy.js new file mode 100644 index 0000000000..f76182f6c8 --- /dev/null +++ b/cypress/e2e/contributors/add-PUBMED-more-50-contrib.cy.js @@ -0,0 +1,51 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add PUBMED ID with more than 50 contributors', async function () { + const pubmedURL = 'https://pubmed.ncbi.nlm.nih.gov/' + const noticeMessage = 'This work has a large number of contributors' + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '45', + it('Add PUBMED ID with more than 50 contributors', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-pubmed').click({ force: true }) + cy.get('#external-id-input') + .clear() + .type(pubmedURL + userData.cyRecordOwner.pubmed_qase45 + '/') + cy.get('[id^=cy-retrieve-work-details]').click() + cy.wait(4000) //need to wait for back end + + //verify link to add one more contrib is not displayed + cy.contains('Add another contributor').should('not.exist') + + //verify the panel is displayed + cy.contains('.notice-panel', noticeMessage).should('exist') + + //save entry + cy.get('#save-work-button').click({ force: true }) + cy.wait(4000) //waiting for back end to complete + + //Verify work was added + cy.get('app-work-stack').should( + 'contain', + userData.cyRecordOwner.pubmed_qase45 + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/add-more-contrib-editing-work.cy.js b/cypress/e2e/contributors/add-more-contrib-editing-work.cy.js index b9f0c8dc34..edfcc994f3 100644 --- a/cypress/e2e/contributors/add-more-contrib-editing-work.cy.js +++ b/cypress/e2e/contributors/add-more-contrib-editing-work.cy.js @@ -35,7 +35,7 @@ describe('Add other contributor while editing work', async function () { cy.get('button[aria-label*="Edit work"]').click() }) //add someone else as contributor with credit role - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(($section) => { cy.get('[formcontrolname="creditName"]') .clear() diff --git a/cypress/e2e/contributors/add-more-editing-work-50-contributors.cy.js b/cypress/e2e/contributors/add-more-editing-work-50-contributors.cy.js index 5e01ad6176..203d33a91a 100644 --- a/cypress/e2e/contributors/add-more-editing-work-50-contributors.cy.js +++ b/cypress/e2e/contributors/add-more-editing-work-50-contributors.cy.js @@ -21,7 +21,7 @@ describe('Add more contributors while editing a work with 50 contributors', asyn cy.get('button[aria-label*="Edit work"]').click() }) //verify link to add one more contrib is disabled - cy.get('#cy-add-another-contributor').should('have.class', 'disabled') + cy.contains('Add another contributor').should('have.class', 'disabled') //verify the panel is displayed cy.contains('.notice-panel', noticeMessage) diff --git a/cypress/e2e/contributors/add-other-contributor-credit-role.cy.js b/cypress/e2e/contributors/add-other-contributor-credit-role.cy.js index 7ca1b55c7f..379ead2b12 100644 --- a/cypress/e2e/contributors/add-other-contributor-credit-role.cy.js +++ b/cypress/e2e/contributors/add-other-contributor-credit-role.cy.js @@ -26,12 +26,9 @@ describe('Other ppl contributions - add contributor with credit role', async fun cy.contains(workType).click() }) cy.get('#title-input').clear().type(title) - cy.get('#cy-work-types').click() - cy.get('#cy-work-types-panel').within(($myOptions) => { - cy.contains(workType).click() - }) + //add someone else as contributor with credit role - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(($section) => { cy.get('[formcontrolname="creditName"]') .clear() diff --git a/cypress/e2e/contributors/add_other_contrib_duplicate_roles.cy.js b/cypress/e2e/contributors/add_other_contrib_duplicate_roles.cy.js index 876c33d5f8..b689e5ac37 100644 --- a/cypress/e2e/contributors/add_other_contrib_duplicate_roles.cy.js +++ b/cypress/e2e/contributors/add_other_contrib_duplicate_roles.cy.js @@ -28,12 +28,9 @@ describe('Add other contributor with duplicate roles - negative testing', async cy.contains(workType).click() }) cy.get('#title-input').clear().type(title) - cy.get('#cy-work-types').click() - cy.get('#cy-work-types-panel').within(($myOptions) => { - cy.contains(workType).click() - }) + //add someone else as contributor with credit role - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(($section) => { cy.get('[formcontrolname="creditName"]') .clear() @@ -46,7 +43,7 @@ describe('Add other contributor with duplicate roles - negative testing', async cy.wait(1000) }) cy.get('app-work-contributors').within(($section) => { - cy.get('#cy-add-another-role').click() + cy.get('.cy-add-another-role').click() cy.get('[formcontrolname="role"]') .contains('No Specified Role') .click({ force: true }) diff --git a/cypress/e2e/contributors/add_other_contrib_multiple_roles.cy.js b/cypress/e2e/contributors/add_other_contrib_multiple_roles.cy.js index 61201034c6..1a6f545204 100644 --- a/cypress/e2e/contributors/add_other_contrib_multiple_roles.cy.js +++ b/cypress/e2e/contributors/add_other_contrib_multiple_roles.cy.js @@ -27,12 +27,9 @@ describe('Other ppl contributions - add contributor with multiple credit roles', cy.contains(workType).click() }) cy.get('#title-input').clear().type(title) - cy.get('#cy-work-types').click() - cy.get('#cy-work-types-panel').within(($myOptions) => { - cy.contains(workType).click() - }) + //add someone else as contributor with credit role - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(($section) => { cy.get('[formcontrolname="creditName"]') .clear() @@ -45,7 +42,7 @@ describe('Other ppl contributions - add contributor with multiple credit roles', cy.wait(1000) }) cy.get('app-work-contributors').within(($section) => { - cy.get('#cy-add-another-role').click() + cy.get('.cy-add-another-role').click() cy.get('[formcontrolname="role"]') .contains('No Specified Role') .click({ force: true }) diff --git a/cypress/e2e/contributors/add_other_contrib_name_max_length.cy.js b/cypress/e2e/contributors/add_other_contrib_name_max_length.cy.js new file mode 100644 index 0000000000..29065cafb5 --- /dev/null +++ b/cypress/e2e/contributors/add_other_contrib_name_max_length.cy.js @@ -0,0 +1,42 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Contributor name field max limit validation', async function () { + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '148', + it('Contributor name field max limit validation', function () { + const otherContributorName = userData.cyRecordOwner.contrib_name_100_chars + const errorMessage = 'Must be less than 100 characters' + + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-manually').click({ force: true }) + + //add someone else as contributor with credit role + cy.get('.cy-add-another-contributor').click() + cy.get('app-work-contributors').within(($section) => { + cy.get('[formcontrolname="creditName"]') + .clear() + .type(otherContributorName) + }) + //click outside input to check for error message + cy.get('.cy-add-another-role').click() + //verify contributor is displayed in details section for this work + cy.contains('mat-error', errorMessage).should('exist') + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/add_other_contrib_no_role.cy.js b/cypress/e2e/contributors/add_other_contrib_no_role.cy.js index f25db277dc..8ebd57b7c6 100644 --- a/cypress/e2e/contributors/add_other_contrib_no_role.cy.js +++ b/cypress/e2e/contributors/add_other_contrib_no_role.cy.js @@ -25,12 +25,9 @@ describe('Other ppl contributions - add contributor with no specific credit role cy.contains(workType).click() }) cy.get('#title-input').clear().type(title) - cy.get('#cy-work-types').click() - cy.get('#cy-work-types-panel').within(($myOptions) => { - cy.contains(workType).click() - }) + //add someone else as contributor with credit role - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(($section) => { cy.get('[formcontrolname="creditName"]') .clear() diff --git a/cypress/e2e/contributors/add_other_contrib_no_role_twice.cy.js b/cypress/e2e/contributors/add_other_contrib_no_role_twice.cy.js index 45cc1b6b56..11150a1d0c 100644 --- a/cypress/e2e/contributors/add_other_contrib_no_role_twice.cy.js +++ b/cypress/e2e/contributors/add_other_contrib_no_role_twice.cy.js @@ -26,18 +26,14 @@ describe('Other ppl contributions - add contributor with no specific credit role cy.contains(workType).click() }) cy.get('#title-input').clear().type(title) - cy.get('#cy-work-types').click() - cy.get('#cy-work-types-panel').within(($myOptions) => { - cy.contains(workType).click() - }) //add someone else as contributor with credit role - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(($section) => { cy.get('[formcontrolname="creditName"]') .clear() .type(otherContributorName) //click to add another role but leave default value - cy.get('#cy-add-another-role').click() + cy.get('.cy-add-another-role').click() }) //save entry diff --git a/cypress/e2e/contributors/bibtex_contrib_and_ignored.cy.js b/cypress/e2e/contributors/bibtex_contrib_and_ignored.cy.js new file mode 100644 index 0000000000..468c0697b4 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_contrib_and_ignored.cy.js @@ -0,0 +1,52 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add work using BibTeX citation where contributor list ends with "and"', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase73_path + const contribName = userData.cyRecordOwner.bibtex_qase73_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '73', + it('Add work using BibTeX citation where contributor list ends with "and"', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Summary view - 'and' is ignored + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Contributors').parent().should('not.contain', 'and') + cy.contains('Show more detail').click() + }) + //Details section - 'and' is ignored + cy.contains('app-display-attribute', 'Contributors').should( + 'not.contain', + 'and' + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_contrib_lastName_firstName.cy.js b/cypress/e2e/contributors/bibtex_contrib_lastName_firstName.cy.js new file mode 100644 index 0000000000..18fcd20dd6 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_contrib_lastName_firstName.cy.js @@ -0,0 +1,48 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add work using BibTeX citation where accents in contributor names are escaped', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase74_path + const contribName = userData.cyRecordOwner.bibtex_qase74_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '74', + it('Add work using BibTeX citation where accents in contributor names are escaped', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Show more details + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Show more detail').click() + }) + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(contribName) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_contrib_lastName_initial.cy.js b/cypress/e2e/contributors/bibtex_contrib_lastName_initial.cy.js new file mode 100644 index 0000000000..eda2e1b6e5 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_contrib_lastName_initial.cy.js @@ -0,0 +1,48 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add work using BibTeX citation where accents in contributor names are escaped', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase75_path + const contribName = userData.cyRecordOwner.bibtex_qase75_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '75', + it('Add work using BibTeX citation where accents in contributor names are escaped', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Show more details + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Show more detail').click() + }) + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(contribName) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_contrib_name_escaped-accents.cy.js b/cypress/e2e/contributors/bibtex_contrib_name_escaped-accents.cy.js new file mode 100644 index 0000000000..d29c077609 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_contrib_name_escaped-accents.cy.js @@ -0,0 +1,49 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add work using BibTeX citation where accents in contributor names are escaped', async function () { + const bibtexFilePath = userData.cyRecordOwner.bibtex_qase68_path + const contribName = userData.cyRecordOwner.bibtex_qase68_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '68', + it('Add work using BibTeX citation where accents in contributor names are escaped', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + cy.wait(2000) //need to wait for back end + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtexFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Show more details + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Show more detail').click() + }) + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(contribName) + }) + ) //end of qase tag + + after(() => { + //log out + // cy.get('#cy-user-info').click({ force: true }) + // cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_contrib_recordOwner_not_added_byDefault.cy.js b/cypress/e2e/contributors/bibtex_contrib_recordOwner_not_added_byDefault.cy.js new file mode 100644 index 0000000000..2875c8a146 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_contrib_recordOwner_not_added_byDefault.cy.js @@ -0,0 +1,54 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Record holder not added as default contributor to works imported from BibTeX', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase76_path + const contribName = userData.cyRecordOwner.bibtex_qase76_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '76', + it('Record holder not added as default contributor to works imported from BibTeX', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Summary view - record owner not added + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Contributors') + .parent() + .should('not.contain', userData.cyRecordOwner.name) + cy.contains('Show more detail').click() + }) + //Details section - - record owner not added + cy.contains('app-display-attribute', 'Contributors').should( + 'not.contain', + userData.cyRecordOwner.name + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_contrib_roles_displayed.cy.js b/cypress/e2e/contributors/bibtex_contrib_roles_displayed.cy.js new file mode 100644 index 0000000000..8ef52cadf6 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_contrib_roles_displayed.cy.js @@ -0,0 +1,49 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Contributor roles are imported from BibTeX citations', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase72_path + const contribName = userData.cyRecordOwner.bibtex_qase72_contrib + const contribRole = userData.cyRecordOwner.bibtex_qase72_contribRole + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '72', + it('Contributor roles are imported from BibTeX citations', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Show more details + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Show more detail').click() + }) + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(contribRole) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_curly_brackets_contrib_name.cy.js b/cypress/e2e/contributors/bibtex_curly_brackets_contrib_name.cy.js new file mode 100644 index 0000000000..31a2479382 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_curly_brackets_contrib_name.cy.js @@ -0,0 +1,48 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add work using BibTeX citation containing curly brackets around contributor names', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase67_path + const contribName = userData.cyRecordOwner.bibtex_qase67_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '67', + it('Add work using BibTeX citation containing curly brackets around contributor names', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Show more details + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Show more detail').click() + }) + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(contribName) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_less_50_contrib.cy.js b/cypress/e2e/contributors/bibtex_less_50_contrib.cy.js new file mode 100644 index 0000000000..e3404691cb --- /dev/null +++ b/cypress/e2e/contributors/bibtex_less_50_contrib.cy.js @@ -0,0 +1,48 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add work using BibTeX citation containing multiple contributors (less than 50)', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase71_path + const contribName = userData.cyRecordOwner.bibtex_qase71_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '71', + it('Add work using BibTeX citation containing multiple contributors (less than 50)', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Show more details + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Show more detail').click() + }) + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(contribName) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_more_50_contrib.cy.js b/cypress/e2e/contributors/bibtex_more_50_contrib.cy.js new file mode 100644 index 0000000000..8631434550 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_more_50_contrib.cy.js @@ -0,0 +1,50 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add work using BibTeX citation containing more than 50 contributors', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase69_path + const contribName = userData.cyRecordOwner.bibtex_qase69_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '69', + it('Add work using BibTeX citation containing more than 50 contributors', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Show more details + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Show more detail').click() + }) + //verify first 50 contrib are displayed + cy.get('app-display-attribute').contains('50') + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(contribName) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/bibtex_single_contrib.cy.js b/cypress/e2e/contributors/bibtex_single_contrib.cy.js new file mode 100644 index 0000000000..1ced1a0a03 --- /dev/null +++ b/cypress/e2e/contributors/bibtex_single_contrib.cy.js @@ -0,0 +1,48 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add work using BibTeX citation containing a single contributor', async function () { + const bibtextFilePath = userData.cyRecordOwner.bibtex_qase70_path + const contribName = userData.cyRecordOwner.bibtex_qase70_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '70', + it('Add work using BibTeX citation containing a single contributor', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-bibtext').click({ force: true }) + //use CYPRESS-UPLOAD-FILE package to bypass OS file picker modal + cy.get('[type="file"]').attachFile(bibtextFilePath) + cy.wait(2000) //need to wait for back end + + //select work + cy.get('[formcontrolname="checked"]').click() + cy.get('#cy-import-works').click() + cy.wait(2000) //load data + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Show more details + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Show more detail').click() + }) + //verify contributor is displayed in details section for this work + cy.get('app-display-attribute').contains(contribName) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/contrib-name-accented-characters.cy.js b/cypress/e2e/contributors/contrib-name-accented-characters.cy.js index 92ee2a1997..f425871277 100644 --- a/cypress/e2e/contributors/contrib-name-accented-characters.cy.js +++ b/cypress/e2e/contributors/contrib-name-accented-characters.cy.js @@ -27,7 +27,7 @@ describe('contributor name - accented characters', async function () { .contains(workType) .click() cy.get('#title-input').clear().type(title) - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(() => { cy.get('[formcontrolname="creditName"]').clear().type(name) //leave default role diff --git a/cypress/e2e/contributors/contrib-name-languages-arabic.cy.js b/cypress/e2e/contributors/contrib-name-languages-arabic.cy.js index f465ddd800..c6735677f1 100644 --- a/cypress/e2e/contributors/contrib-name-languages-arabic.cy.js +++ b/cypress/e2e/contributors/contrib-name-languages-arabic.cy.js @@ -27,7 +27,7 @@ describe('contributor name - language - Arabic', async function () { .contains(workType) .click() cy.get('#title-input').clear().type(title) - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(() => { cy.get('[formcontrolname="creditName"]').clear().type(name) //leave default role diff --git a/cypress/e2e/contributors/contrib-name-languages-chinese.cy.js b/cypress/e2e/contributors/contrib-name-languages-chinese.cy.js index 1b09773d33..087bcf5f37 100644 --- a/cypress/e2e/contributors/contrib-name-languages-chinese.cy.js +++ b/cypress/e2e/contributors/contrib-name-languages-chinese.cy.js @@ -27,7 +27,7 @@ describe('contributor name - language - Chinese', async function () { .contains(workType) .click() cy.get('#title-input').clear().type(title) - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(() => { cy.get('[formcontrolname="creditName"]').clear().type(name) //leave default role diff --git a/cypress/e2e/contributors/contrib-name-languages-russian.cy.js b/cypress/e2e/contributors/contrib-name-languages-russian.cy.js index 1824492cbc..76fec50454 100644 --- a/cypress/e2e/contributors/contrib-name-languages-russian.cy.js +++ b/cypress/e2e/contributors/contrib-name-languages-russian.cy.js @@ -27,7 +27,7 @@ describe('contributor name - language - Russian', async function () { .contains(workType) .click() cy.get('#title-input').clear().type(title) - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() cy.get('app-work-contributors').within(() => { cy.get('[formcontrolname="creditName"]').clear().type(name) //leave default role diff --git a/cypress/e2e/contributors/contrib-name-required-field.cy.js b/cypress/e2e/contributors/contrib-name-required-field.cy.js index 7746498147..3ef7516d97 100644 --- a/cypress/e2e/contributors/contrib-name-required-field.cy.js +++ b/cypress/e2e/contributors/contrib-name-required-field.cy.js @@ -27,7 +27,7 @@ describe('contributor name field is required', async function () { .contains(workType) .click() cy.get('#title-input').clear().type(title) - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() //save entry cy.get('#save-work-button').wait(1000).click({ force: true }) diff --git a/cypress/e2e/contributors/contributor-counter-automatically-updated.cy.js b/cypress/e2e/contributors/contributor-counter-automatically-updated.cy.js index daa28d8a21..70ceb23faf 100644 --- a/cypress/e2e/contributors/contributor-counter-automatically-updated.cy.js +++ b/cypress/e2e/contributors/contributor-counter-automatically-updated.cy.js @@ -20,10 +20,16 @@ describe('Counter in contributors section updates accordingly while adding a wor //only record owner listed cy.get('app-work-contributors').contains('Contributors to this work (1)') //add a second contributor - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() + cy.get('#draggable-1').within(() => { + cy.get('[formcontrolname="creditName"]').clear().type('Name2') + }) cy.get('app-work-contributors').contains('Contributors to this work (2)') //add a third contributor - cy.get('#cy-add-another-contributor').click() + cy.get('.cy-add-another-contributor').click() + cy.get('#draggable-2').within(() => { + cy.get('[formcontrolname="creditName"]').clear().type('Name3') + }) cy.get('app-work-contributors').contains('Contributors to this work (3)') //remove second contributor diff --git a/cypress/e2e/contributors/contributor-name-100-chars.cy.js b/cypress/e2e/contributors/contributor-name-100-chars.cy.js new file mode 100644 index 0000000000..1892eccb35 --- /dev/null +++ b/cypress/e2e/contributors/contributor-name-100-chars.cy.js @@ -0,0 +1,52 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('contributor name with 100 characters', async function () { + const workType = 'Book' + const title = 'Cypress test contributors 23' + const name = + 'namehas100charsnamehas100charsnamehas100charsnamehas100charsnamehas100charsnamehas100charsnamehas100' + const errorTxt = 'Must be less than 100 characters' + + before(() => {}) + + qase( + '23', + it('contributor name with 100 characters (99)', function () { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + + //go to add work form + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-manually').click({ force: true }) + cy.wait(1000) + cy.get('[formcontrolname="workType"]').click() + cy.get('[role="listbox"], [aria-label="work-type-label"]') + .contains(workType) + .click() + cy.get('#title-input').clear().type(title) + cy.get('.cy-add-another-contributor').click() + cy.get('app-work-contributors').within(() => { + cy.get('[formcontrolname="creditName"]').clear().type(name) + //leave default role + }) + //save entry + cy.get('#save-work-button').wait(1000).click({ force: true }) + + //Verify error message is displayed + cy.get('mat-error').contains(errorTxt).should('be.visible') + //cancel + cy.get('#cancel-work-button').click() + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/contributor-name-99-chars.cy.js b/cypress/e2e/contributors/contributor-name-99-chars.cy.js new file mode 100644 index 0000000000..e74f6e4ed3 --- /dev/null +++ b/cypress/e2e/contributors/contributor-name-99-chars.cy.js @@ -0,0 +1,51 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('contributor name with less than 100 characters', async function () { + const workType = 'Book' + const title = 'Cypress test contributors 24' + const name = + 'namehas99charsnamehas99charsnamehas99charsnamehas99charsnamehas99charsnamehas99charsnamehas99chars!' + + before(() => {}) + + qase( + '24', + it('contributor name with less than 100 characters (99)', function () { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + + //go to add work form + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-manually').click({ force: true }) + cy.wait(1000) + cy.get('[formcontrolname="workType"]').click() + cy.get('[role="listbox"], [aria-label="work-type-label"]') + .contains(workType) + .click() + cy.get('#title-input').clear().type(title) + cy.get('.cy-add-another-contributor').click() + cy.get('app-work-contributors').within(() => { + cy.get('[formcontrolname="creditName"]').clear().type(name) + //leave default role + }) + //save entry + cy.get('#save-work-button').wait(1000).click({ force: true }) + + //Verify work was added + cy.get('#cy-works', { timeout: 10000 }).should('contain', title) + //Verify name is displayed + cy.get('#cy-works', { timeout: 10000 }).should('contain', name) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/contributor-name-over-100-chars.cy.js b/cypress/e2e/contributors/contributor-name-over-100-chars.cy.js new file mode 100644 index 0000000000..a2ce942dd2 --- /dev/null +++ b/cypress/e2e/contributors/contributor-name-over-100-chars.cy.js @@ -0,0 +1,52 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('contributor name over 100 characters', async function () { + const workType = 'Book' + const title = 'Cypress test contributors 22' + const name = + 'nameover100charnameover100charnameover100charnameover100charnameover100charnameover100charnameover100' + const errorTxt = 'Must be less than 100 characters' + + before(() => {}) + + qase( + '22', + it('contributor name over 100 characters (99)', function () { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + + //go to add work form + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-manually').click({ force: true }) + cy.wait(1000) + cy.get('[formcontrolname="workType"]').click() + cy.get('[role="listbox"], [aria-label="work-type-label"]') + .contains(workType) + .click() + cy.get('#title-input').clear().type(title) + cy.get('.cy-add-another-contributor').click() + cy.get('app-work-contributors').within(() => { + cy.get('[formcontrolname="creditName"]').clear().type(name) + //leave default role + }) + //save entry + cy.get('#save-work-button').wait(1000).click({ force: true }) + + //Verify error message is displayed + cy.get('mat-error').contains(errorTxt).should('be.visible') + //cancel + cy.get('#cancel-work-button').click() + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/delegate_adds_work.cy.js b/cypress/e2e/contributors/delegate_adds_work.cy.js new file mode 100644 index 0000000000..91e950df0f --- /dev/null +++ b/cypress/e2e/contributors/delegate_adds_work.cy.js @@ -0,0 +1,61 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Account delegate adds work with contributors', async function () { + before(() => { + cy.intercept('POST', '/switch-user?**').as('switchUser') + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyDelegate) + cy.wait(2000) + }) + + qase( + '34', + it('Account delegate adds work with contributors', function () { + const workType = 'Book' + const title = 'Cypress test contributors 34 - delegate' + + cy.contains('a', 'Switch to').click({ force: true }) + cy.contains(userData.cyRecordOwner.oid).click({ force: true }) + cy.wait('@switchUser') + //work around for cypress to aknowledge we switched users + cy.visit('/my-orcid') + //verify we switched to record owner account + cy.get('#status-bar').should('contain', userData.cyRecordOwner.oid) + + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-manually').click({ force: true }) + cy.get('#cy-work-types').click() + cy.get('#cy-work-types-panel').within(($myOptions) => { + cy.contains(workType).click() + }) + cy.get('#title-input').clear().type(title) + + //save entry + cy.get('#save-work-button').wait(1000).click({ force: true }) + cy.wait(2000) + + //Verify work was added for the record owner not the delegate + cy.contains('app-work-stack', title).within(($thisWork) => { + cy.contains('Show more detail').click() + cy.get('app-display-attribute').should( + 'contain', + userData.cyRecordOwner.name, + { + matchCase: false, + } + ) + }) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/doi_add_self_as_contrib.cy.js b/cypress/e2e/contributors/doi_add_self_as_contrib.cy.js new file mode 100644 index 0000000000..f4156e5b87 --- /dev/null +++ b/cypress/e2e/contributors/doi_add_self_as_contrib.cy.js @@ -0,0 +1,56 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add self as contributor to work added from DOI', async function () { + const extId = userData.cyRecordOwner.doi_qase85_extId + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '85', + it('Add self as contributor to work added from DOI', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-doi').click({ force: true }) + cy.get('#external-id-input').clear().type(extId) + cy.get('[id^="cy-retrieve-work-details"]').click() + cy.wait(2000) + + //add self as contributor + cy.get('#cy-add-record-holder-contributor').click() + cy.wait(2000) + //verify contributors loaded + cy.contains('.credit-name-and-roles', userData.cyRecordOwner.name).should( + 'exist' + ) + + cy.get('#save-work-button').click({ force: true }) + cy.wait(4000) //waiting for backend + + //Summary view - record owner not added + cy.contains('app-panel-data', extId).within(($thisWork) => { + cy.contains('Contributors') + .parent() + .should('include.text', userData.cyRecordOwner.name) + cy.contains('Show more detail').click() + }) + //Details section - - record owner not added + cy.contains('app-display-attribute', 'Contributors').should( + 'include.text', + userData.cyRecordOwner.name + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/doi_contrib_recordOwner_not_added_byDefault.cy.js b/cypress/e2e/contributors/doi_contrib_recordOwner_not_added_byDefault.cy.js new file mode 100644 index 0000000000..560123ac1a --- /dev/null +++ b/cypress/e2e/contributors/doi_contrib_recordOwner_not_added_byDefault.cy.js @@ -0,0 +1,54 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Record holder not added as default contributor to works imported from DOI', async function () { + const extId = userData.cyRecordOwner.doi_qase77_extId + const contribName = userData.cyRecordOwner.doi_qase77_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '77', + it('Record holder not added as default contributor to works imported from DOI', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-doi').click({ force: true }) + cy.get('#external-id-input').clear().type(extId) + cy.get('[id^="cy-retrieve-work-details"]').click() + cy.wait(2000) + + //verify contributors loaded + cy.contains('.credit-name-and-roles', contribName).should('exist') + cy.get('#save-work-button').click() + cy.wait(2000) //waiting for backend + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Summary view - record owner not added + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Contributors') + .parent() + .should('not.contain', userData.cyRecordOwner.name) + cy.contains('Show more detail').click() + }) + //Details section - - record owner not added + cy.contains('app-display-attribute', 'Contributors').should( + 'not.contain', + userData.cyRecordOwner.name + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/manually-add-contributor-credit-role.cy.js b/cypress/e2e/contributors/manually-add-contributor-credit-role.cy.js index 021d37d1f6..7e6761658a 100644 --- a/cypress/e2e/contributors/manually-add-contributor-credit-role.cy.js +++ b/cypress/e2e/contributors/manually-add-contributor-credit-role.cy.js @@ -27,18 +27,15 @@ describe('Manually add work with contributor credit role', async function () { cy.contains(workType).click() }) cy.get('#title-input').clear().type(title) - cy.get('#cy-work-types').click() - cy.get('#cy-work-types-panel').within(($myOptions) => { - cy.contains(workType).click() - }) cy.get('[formcontrolname="role"]').click() //TO DO: replace locators with ids - cy.get('[aria-label="Please select a role"]').within(($myOptions) => { - cy.contains(creditRole).click() - }) - + cy.get('[role="listbox"][aria-label="Please select a role"]').within( + ($myOptions) => { + cy.contains(creditRole).click() + } + ) //save entry cy.get('#save-work-button').wait(1000).click({ force: true }) - + cy.wait(1000) //wait for the page to refresh //Verify work was added cy.get('#cy-works', { timeout: 10000 }).should('contain', title) diff --git a/cypress/e2e/contributors/pubMed_contrib_recordOwner_not_added_byDefault.cy.js b/cypress/e2e/contributors/pubMed_contrib_recordOwner_not_added_byDefault.cy.js new file mode 100644 index 0000000000..97a7680707 --- /dev/null +++ b/cypress/e2e/contributors/pubMed_contrib_recordOwner_not_added_byDefault.cy.js @@ -0,0 +1,57 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Record holder not added as default contributor to works imported from PubMed', async function () { + const pubMediaUrl = 'https://pubmed.ncbi.nlm.nih.gov/' + const extId = userData.cyRecordOwner.pubMed_qase78_extId + const contribName = userData.cyRecordOwner.pubMed_qase78_contrib + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '78', + it('Record holder not added as default contributor to works imported from PubMed', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-pubmed').click({ force: true }) + cy.get('#external-id-input') + .clear() + .type(pubMediaUrl + extId) + cy.get('[id^="cy-retrieve-work-details"]').click() + cy.wait(4000) //waiting for backend + + //verify contributors loaded + cy.contains('.credit-name-and-roles', contribName).should('exist') + cy.get('#save-work-button').click() + cy.wait(4000) //waiting for backend + + //Verify work was added with contrib in summary + cy.get('app-work-stack').should('contain', contribName) + + //Summary view - record owner not added + cy.contains('app-panel-data', contribName).within(($thisWork) => { + cy.contains('Contributors') + .parent() + .should('not.contain', userData.cyRecordOwner.name) + cy.contains('Show more detail').click() + }) + //Details section - - record owner not added + cy.contains('app-display-attribute', 'Contributors').should( + 'not.contain', + userData.cyRecordOwner.name + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/contributors/pubMedia_add_self_as_contrib.cy.js b/cypress/e2e/contributors/pubMedia_add_self_as_contrib.cy.js new file mode 100644 index 0000000000..b134a971cc --- /dev/null +++ b/cypress/e2e/contributors/pubMedia_add_self_as_contrib.cy.js @@ -0,0 +1,59 @@ +/// + +import userData from '../../fixtures/contributors-fixtures/contributors-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Add self as contributor to work added from PubMed ID', async function () { + const pubMediaUrl = 'https://pubmed.ncbi.nlm.nih.gov/' + const extId = userData.cyRecordOwner.pubMed_qase86_extId + + before(() => { + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyRecordOwner) + }) + + qase( + '86', + it('Add self as contributor to work added from PubMed ID', function () { + cy.get('#cy-works').within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-pubmed').click({ force: true }) + cy.get('#external-id-input') + .clear() + .type(pubMediaUrl + extId) + cy.get('[id^="cy-retrieve-work-details"]').click() + cy.wait(2000) + + //add self as contributor + cy.get('#cy-add-record-holder-contributor').click() + cy.wait(2000) + //verify contributors loaded + cy.contains('.credit-name-and-roles', userData.cyRecordOwner.name).should( + 'exist' + ) + + cy.get('#save-work-button').click({ force: true }) + cy.wait(4000) //waiting for backend + + //Summary view - record owner not added + cy.contains('app-panel-data', extId).within(($thisWork) => { + cy.contains('Contributors') + .parent() + .should('include.text', userData.cyRecordOwner.name) + cy.contains('Show more detail').click() + }) + //Details section - - record owner not added + cy.contains('app-display-attribute', 'Contributors').should( + 'include.text', + userData.cyRecordOwner.name + ) + }) + ) //end of qase tag + + after(() => { + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/inbox_notifications/employment-inbox-notif.cy.js b/cypress/e2e/inbox_notifications/employment-inbox-notif.cy.js index 232fc3f2aa..afd128201d 100644 --- a/cypress/e2e/inbox_notifications/employment-inbox-notif.cy.js +++ b/cypress/e2e/inbox_notifications/employment-inbox-notif.cy.js @@ -35,22 +35,22 @@ describe('Inbox: add/update/delete Employment via API', async function () { const curlPutEmployment = "curl -i -H 'Content-type: application/json' -H 'Authorization: Bearer " + userData.cyNotifPerm.clientBearer + - "' -X PUT -d '" + + "' -d '" + userData.cyNotifPerm.curlEmploymentUpdatePath + - "' " + + "' -X PUT '" + Cypress.env('membersAPI_URL') + userData.cyNotifPerm.oid + Cypress.env('membersAPI_employmentEndpoint') + - '/' //here append "{PUTCODE}" + '/' //here append "{PUTCODE}"+ "'" const curlDeleteEmployment = "curl -i -H 'Content-type: application/json' -H 'Authorization: Bearer " + userData.cyNotifPerm.clientBearer + - "' -X DELETE " + + "' -X DELETE '" + Cypress.env('membersAPI_URL') + userData.cyNotifPerm.oid + Cypress.env('membersAPI_employmentEndpoint') + - '/' //here append "{PUTCODE}" + '/' //here append "{PUTCODE}"+ "'" before(() => { //log in @@ -99,9 +99,8 @@ describe('Inbox: add/update/delete Employment via API', async function () { cy.exec(curlReadSingleEmployment + putCode + "'").then( (singleEmploymentResp) => { //remove non json header from string - const jsonStartIndex = singleEmploymentResp.stdout.indexOf( - '{"created-date"' - ) //where does the json start? + const jsonStartIndex = + singleEmploymentResp.stdout.indexOf('{"created-date"') //where does the json start? updatedContent = singleEmploymentResp.stdout.substring(jsonStartIndex) //update the employment: make a change in the content updatedContent = updatedContent.replace( @@ -114,7 +113,7 @@ describe('Inbox: add/update/delete Employment via API', async function () { ) //Client Updates the employment - cy.exec(curlPutEmployment + putCode).then((responsePUT) => { + cy.exec(curlPutEmployment + putCode + "'").then((responsePUT) => { //verify curl was executed successfully expect(responsePUT.code).to.eq(0) //verify http response status is successful: 200 @@ -127,7 +126,7 @@ describe('Inbox: add/update/delete Employment via API', async function () { cy.contains('Updated').should('be.visible') //Client deletes employment with that put code - cy.exec(curlDeleteEmployment + putCode).then((responseDelete) => { + cy.exec(curlDeleteEmployment + putCode + "'").then((responseDelete) => { //verify curl was executed successfully expect(responseDelete.code).to.eq(0) //verify http response status is successful: 204 diff --git a/cypress/e2e/inbox_notifications/funding-inbox-notif.cy.js b/cypress/e2e/inbox_notifications/funding-inbox-notif.cy.js index 8390ae1253..798fb77f48 100644 --- a/cypress/e2e/inbox_notifications/funding-inbox-notif.cy.js +++ b/cypress/e2e/inbox_notifications/funding-inbox-notif.cy.js @@ -35,9 +35,9 @@ describe('Inbox: add/update/delete funding via API', async function () { const curlPutFunding = "curl -i -H 'Content-type: application/json' -H 'Authorization: Bearer " + userData.cyNotifPerm.clientBearer + - "' -X PUT -d '" + + "' -d '" + userData.cyNotifPerm.curlFundingUpdatePath + - "' " + + "' -X PUT '" + Cypress.env('membersAPI_URL') + userData.cyNotifPerm.oid + Cypress.env('membersAPI_fundingsEndpoint') + @@ -46,7 +46,7 @@ describe('Inbox: add/update/delete funding via API', async function () { const curlDeleteFunding = "curl -i -H 'Content-type: application/json' -H 'Authorization: Bearer " + userData.cyNotifPerm.clientBearer + - "' -X DELETE " + + "' -X DELETE '" + Cypress.env('membersAPI_URL') + userData.cyNotifPerm.oid + Cypress.env('membersAPI_fundingsEndpoint') + @@ -65,7 +65,7 @@ describe('Inbox: add/update/delete funding via API', async function () { let putCode let updatedContent - //Client adds an work + //Client adds funding cy.exec(curlAddFunding).then((response) => { //verify curl was executed successfully expect(response.code).to.eq(0) @@ -81,7 +81,7 @@ describe('Inbox: add/update/delete funding via API', async function () { cy.get('button').contains('Archive').click() //Read works to grab putcode - //There should only be one work + //There should only be one funding cy.exec(curlReadAllFundings).then((response) => { //verify curl was executed successfully expect(response.code).to.eq(0) @@ -114,7 +114,7 @@ describe('Inbox: add/update/delete funding via API', async function () { }) //Client Updates the work - cy.exec(curlPutFunding + putCode).then((responsePUT) => { + cy.exec(curlPutFunding + putCode + "'").then((responsePUT) => { //verify curl was executed successfully expect(responsePUT.code).to.eq(0) //verify http response status is successful: 200 @@ -127,7 +127,7 @@ describe('Inbox: add/update/delete funding via API', async function () { cy.contains('Updated').should('be.visible') //Client deletes work with that put code - cy.exec(curlDeleteFunding + putCode).then((responseDelete) => { + cy.exec(curlDeleteFunding + putCode + "'").then((responseDelete) => { //verify curl was executed successfully expect(responseDelete.code).to.eq(0) //verify http response status is successful: 204 diff --git a/cypress/e2e/inbox_notifications/work-no-update-no-notif.cy.js b/cypress/e2e/inbox_notifications/work-no-update-no-notif.cy.js new file mode 100644 index 0000000000..3a76d523e7 --- /dev/null +++ b/cypress/e2e/inbox_notifications/work-no-update-no-notif.cy.js @@ -0,0 +1,147 @@ +/// + +import userData from '../../fixtures/inboxNotif-users.fixture.json' + +describe('Inbox: no notification sent when update has no changes', async function () { + const curlAddWork = + "curl -i -H 'Content-type: application/json' -H 'Authorization: Bearer " + + userData.cyNotifPerm.clientBearer + + "' -d '" + + userData.cyNotifPerm.curlWorkPath + + "' -X POST '" + + Cypress.env('membersAPI_URL') + + userData.cyNotifPerm.oid + + Cypress.env('membersAPI_workEndpoint') + + "'" + + const curlReadAllWorks = + "curl -i -H 'Accept: application/json' -H 'Authorization: Bearer " + + userData.cyNotifPerm.clientBearer + + "' -X GET '" + + Cypress.env('membersAPI_URL') + + userData.cyNotifPerm.oid + + Cypress.env('membersAPI_allWorksEndpoint') + + "'" + + const curlReadSingleWork = + "curl -i -H 'Accept: application/json' -H 'Authorization: Bearer " + + userData.cyNotifPerm.clientBearer + + "' -X GET '" + + Cypress.env('membersAPI_URL') + + userData.cyNotifPerm.oid + + Cypress.env('membersAPI_workEndpoint') + + '/' //here append "{PUTCODE}" + "'" + + const curlPutWork = + "curl -i -H 'Content-type: application/json' -H 'Authorization: Bearer " + + userData.cyNotifPerm.clientBearer + + "' -X PUT -d '" + + userData.cyNotifPerm.curlWorkUpdatePath + + "' " + + Cypress.env('membersAPI_URL') + + userData.cyNotifPerm.oid + + Cypress.env('membersAPI_workEndpoint') + + '/' //here append "{PUTCODE}" + + const curlDeleteWork = + "curl -i -H 'Content-type: application/json' -H 'Authorization: Bearer " + + userData.cyNotifPerm.clientBearer + + "' -X DELETE " + + Cypress.env('membersAPI_URL') + + userData.cyNotifPerm.oid + + Cypress.env('membersAPI_workEndpoint') + + '/' //here append "{PUTCODE}" + + before(() => { + //log in + cy.visit(Cypress.env('signInURL')) + cy.signin(userData.cyNotifPerm) + //go to inbox + cy.get('#cy-user-info').click() + cy.get('#cy-inbox').wait(1000).click({ force: true }) + }) + + it('Inbox notifications are not sent when update is sent with no actual changes', function () { + let putCode + let updatedContent + + //Client adds an work + cy.exec(curlAddWork).then((response) => { + //verify curl was executed successfully + expect(response.code).to.eq(0) + //verify http response status is successful: 201 + expect(response.stdout).to.contain('HTTP/2 201') + }) + + //#1 check inbox has the notification for adding + cy.reload() + cy.wait(2000) + cy.get('app-notification').contains('YOUR RECORD').click() + cy.contains('Added').should('be.visible') + cy.get('button').contains('Archive').click() + + //Read works to grab putcode + //There should only be one work + cy.exec(curlReadAllWorks).then((response) => { + //verify curl was executed successfully + expect(response.code).to.eq(0) + //verify http response status is successful: 200 + expect(response.stdout).to.contain('HTTP/2 200') + //grab put code + const responseString = response.stdout + const putcodeIndex = responseString.indexOf('put-code":') + const putCodeStartPosition = putcodeIndex + 10 // +length + const putCodeEndPosition = responseString.indexOf(',"created-date') + putCode = responseString.substring( + putCodeStartPosition, + putCodeEndPosition + ) + cy.exec(curlReadSingleWork + putCode + "'").then((singleWorkResp) => { + //remove non json header from string + const jsonStartIndex = singleWorkResp.stdout.indexOf('{"created-date"') //where does the json start? + updatedContent = singleWorkResp.stdout.substring(jsonStartIndex) + //do not make any changes + //write the file to use for the PUT + cy.writeFile(userData.cyNotifPerm.work_putWriteFilePath, updatedContent) + }) + + //Client Updates the work + cy.exec(curlPutWork + putCode).then((responsePUT) => { + //verify curl was executed successfully + expect(responsePUT.code).to.eq(0) + //verify http response status is successful: 200 + expect(responsePUT.stdout).to.contain('HTTP/2 200') + }) + //#2 verify no notification is received for the PUT + cy.reload() + cy.wait(2000) + cy.get('app-notification').contains('YOUR RECORD').click() + cy.contains('Updated').should('not.exist') + + //Client deletes work with that put code + cy.exec(curlDeleteWork + putCode).then((responseDelete) => { + //verify curl was executed successfully + expect(responseDelete.code).to.eq(0) + //verify http response status is successful: 204 + expect(responseDelete.stdout).to.contain('HTTP/2 204') + }) + //#3 verify the notification was received for deleting + cy.reload() + cy.wait(2000) + cy.get('app-notification').contains('YOUR RECORD').click() + cy.contains('Deleted').should('be.visible') + }) + }) + + after(() => { + //CLEAN INBOX: archive all notifications + cy.get('[class="control-container"]').within(() => { + cy.get('mat-checkbox').click() + }) + cy.get('button').contains('Archive').click() + cy.wait(2000) //wait for back end to complete + //log out + cy.get('#cy-user-info').click({ force: true }) + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/my-orcid/add-funding-manually.cy.js b/cypress/e2e/my-orcid/add-funding-manually.cy.js index 953d125792..e76b73e923 100644 --- a/cypress/e2e/my-orcid/add-funding-manually.cy.js +++ b/cypress/e2e/my-orcid/add-funding-manually.cy.js @@ -38,7 +38,9 @@ describe('My orcid - users are able to add funding info in their record', async cy.contains(fundingData.tranlationLanguage).click() }) cy.get('#funding-project-link-input').clear().type(fundingData.fundingLink) - cy.get('#description-input').clear().type(fundingData.description) + cy.get('[formcontrolname="description"]') + .clear() + .type(fundingData.description) cy.get('[formcontrolname="currencyCode"]').click() cy.get('[role="listbox"]').within(($currency) => { //TO DO: replace with id for the element when we add it @@ -74,8 +76,8 @@ describe('My orcid - users are able to add funding info in their record', async }) //add identifier cy.contains('Add an identifier').click() //TO DO: replace with id for the element when we add it - cy.get('#grant-number-input').clear().type(uniqueIdentifier) - cy.get('#grant-url-input').clear().type(fundingData.grantLink) + cy.get('[formcontrolname="grantNumber"]').clear().type(uniqueIdentifier) + cy.get('[formcontrolname="grantUrl"]').clear().type(fundingData.grantLink) //save entry cy.get('#save-names-button').click() diff --git a/cypress/e2e/my-orcid/add-work-doi.cy.js b/cypress/e2e/my-orcid/add-work-doi.cy.js index 9495174808..0829a4e77a 100644 --- a/cypress/e2e/my-orcid/add-work-doi.cy.js +++ b/cypress/e2e/my-orcid/add-work-doi.cy.js @@ -1,6 +1,7 @@ /// import testData from '../../fixtures/affiliations-testing-data.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' describe('My orcid - users are able to edit work info in their record', async function () { before(() => { @@ -8,23 +9,25 @@ describe('My orcid - users are able to edit work info in their record', async fu cy.visit('/my-orcid') }) - it('User adds work by DOI and without modifying manually', function () { - const testWorks = testData.affilliantionWorks + qase( + '79', + it('User adds work by DOI and without modifying manually', function () { + const testWorks = testData.affilliantionWorks - cy.get('#cy-works', { timeout: 6000 }).within(($myPanel) => { - cy.get('#cy-menu-add-works').click() + cy.get('#cy-works', { timeout: 6000 }).within(($myPanel) => { + cy.get('#cy-menu-add-works').click() + }) + cy.get('#cy-add-work-doi').click({ force: true }) + cy.get('#external-id-input').clear().type(testWorks.DOI) + cy.get(`[id^='cy-retrieve-work-details']`).click() + cy.get('#save-work-button').wait(1000).click({ force: true }) //wait for modal to display + //Verify work was added + cy.get('#cy-works', { timeout: 6000 }).should( + 'contain', + testWorks.workTitleDOI + ) }) - cy.get('#cy-add-work-doi').click({ force: true }) - cy.get('#external-id-input').clear().type(testWorks.DOI) - cy.get(`[id^='cy-retrieve-work-details']`).click() - cy.get('#save-work-button').wait(1000).click({ force: true }) //wait for modal to display - //Verify work was added - cy.get('#cy-works', { timeout: 6000 }).should( - 'contain', - testWorks.workTitleDOI - ) - }) - + ) //end of qase tag after(() => { //log out cy.get('#cy-user-info').click() diff --git a/cypress/e2e/my-orcid/add-work-manually.cy.js b/cypress/e2e/my-orcid/add-work-manually.cy.js index 6522b35493..107db68071 100644 --- a/cypress/e2e/my-orcid/add-work-manually.cy.js +++ b/cypress/e2e/my-orcid/add-work-manually.cy.js @@ -52,7 +52,9 @@ describe('My orcid - users are able to edit work info in their record', async fu cy.contains(testWorks.manuallyCitationType).click() }) cy.get('#citation-input').clear().type(testWorks.manuallyCitation) - cy.get('#description-input').clear().type(testWorks.manuallyCitationDesc) + cy.get('[formcontrolname="shortDescription"]') + .clear() + .type(testWorks.manuallyCitationDesc) //add identifier cy.get('#cy-add-an-work-external-id').click() cy.get('[formcontrolname="externalIdentifierType"]').click() //to do REPLACE with id for the element next sprint @@ -60,10 +62,10 @@ describe('My orcid - users are able to edit work info in their record', async fu //to do REPLACE with id for the element next sprint cy.contains(testWorks.manuallyIdentifierType).click() }) - cy.get('#external-identifier-id-input') + cy.get('[formcontrolname="externalIdentifierId"]') .clear() .type(testWorks.manuallyIdentifierId) - cy.get('#external-identifier-url-input') + cy.get('[formcontrolname="externalIdentifierUrl"]') .clear() .type(testWorks.manuallyIdentifierLink) //by default Self relationship is checked diff --git a/cypress/e2e/my-orcid/not-verified-email-can-edit-emails.cy.js b/cypress/e2e/my-orcid/not-verified-email-can-edit-emails.cy.js new file mode 100644 index 0000000000..648fbaaa2b --- /dev/null +++ b/cypress/e2e/my-orcid/not-verified-email-can-edit-emails.cy.js @@ -0,0 +1,54 @@ +/// + +import userData from '../../fixtures/testing-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('User without verified primary email address can edit emails section', async function () { + const addEmail = 'cypressusertesting@orcid.org' + + before(() => { + cy.visit('/') + }) + + qase( + '108', + it('User without verified primary email address can edit emails section', function () { + //click Sign in + cy.get('#menu-signin-button').click() + + //sign in with registered user + cy.get('#username') + .clear() + .type(userData.cyUserPrimaryEmailNotVerified.oid) + cy.get('#password') + .clear() + .type(userData.cyUserPrimaryEmailNotVerified.password) + cy.get('#signin-button').click() + + //click on edit pencil for Emails section + cy.get('#emails-panel').within(($myPanel) => { + cy.get('.cy-edit-button').click() + }) + + cy.get('#add-link').click() + + cy.get('#newEmailInput1').clear().type(addEmail) + cy.get('#save-emails-button').wait(1000).click() + + //verify the keyword is displayed + cy.get('#emails-panel') + .within(($section) => { + cy.get('[class="line"]') + }) + .should('contain', addEmail) + + //clean up state + cy.cleanEmails() + }) + ) //end of qase tag + after(() => { + //log out + cy.get('#cy-user-info').click() + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/my-orcid/not-verified-email-can-edit-names.cy.js b/cypress/e2e/my-orcid/not-verified-email-can-edit-names.cy.js new file mode 100644 index 0000000000..bbc70740f7 --- /dev/null +++ b/cypress/e2e/my-orcid/not-verified-email-can-edit-names.cy.js @@ -0,0 +1,50 @@ +/// + +import userData from '../../fixtures/testing-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('User without verified primary email address can edit Names section', async function () { + const addPublishedName = 'Cypress Published Name' + + before(() => { + cy.visit('/') + }) + + qase( + '107', + it('User without verified primary email address can edit Names section', function () { + //click Sign in + cy.get('#menu-signin-button').click() + + //sign in with registered user + cy.get('#username') + .clear() + .type(userData.cyUserPrimaryEmailNotVerified.oid) + cy.get('#password') + .clear() + .type(userData.cyUserPrimaryEmailNotVerified.password) + cy.get('#signin-button').click() + + cy.get('#names-panel').within(($namePanel) => { + cy.get('.cy-edit-button').click() + }) + cy.get('#published-names-input').wait(1000).clear().type(addPublishedName) + cy.get('#save-names-button').click() + cy.wait(2000) //wait for back end to complete + + //verify the name is displayed + cy.get('#publishedName').then(($content) => { + let textFound = $content.text() + cy.log(textFound) + textFound = textFound.trim() + cy.log(textFound) + expect(textFound).equal(addPublishedName) + }) + }) + ) //end of qase tag + after(() => { + //log out + cy.get('#cy-user-info').click() + cy.get('#cy-signout').click({ force: true }) + }) +}) diff --git a/cypress/e2e/my-orcid/public-page-visibility-no-public-affiliations.cy.js b/cypress/e2e/my-orcid/public-page-visibility-no-public-affiliations.cy.js index c4f7f5d58d..8fceae7a01 100644 --- a/cypress/e2e/my-orcid/public-page-visibility-no-public-affiliations.cy.js +++ b/cypress/e2e/my-orcid/public-page-visibility-no-public-affiliations.cy.js @@ -5,7 +5,7 @@ import userData from '../../fixtures/testing-users.fixture.json' describe('Public record page: validate data limited or private is not displayed', async function () { const testUser = userData.cyUserPublicPageNoAffiliations - before(() => { + beforeEach(() => { cy.visit('/' + testUser.oid) }) diff --git a/cypress/e2e/my-orcid/public-page-visibility-public-affiliations.cy.js b/cypress/e2e/my-orcid/public-page-visibility-public-affiliations.cy.js index daedc84fe4..25e4c3c8ef 100644 --- a/cypress/e2e/my-orcid/public-page-visibility-public-affiliations.cy.js +++ b/cypress/e2e/my-orcid/public-page-visibility-public-affiliations.cy.js @@ -5,7 +5,7 @@ import userData from '../../fixtures/testing-users.fixture.json' describe('Public record page: validate public affiliations are displayed', async function () { var testUser = userData.cyUserPublicPagePublicAffiliations - before(() => { + beforeEach(() => { cy.visit('/' + testUser.oid) }) diff --git a/cypress/e2e/my-orcid/sidebar-add-websites-negative-testing.cy.js b/cypress/e2e/my-orcid/sidebar-add-websites-negative-testing.cy.js index e3be7ed124..475932d2b3 100644 --- a/cypress/e2e/my-orcid/sidebar-add-websites-negative-testing.cy.js +++ b/cypress/e2e/my-orcid/sidebar-add-websites-negative-testing.cy.js @@ -4,14 +4,18 @@ import userData from '../../fixtures/testing-users.fixture.json' import testingData from '../../fixtures/negative-testing-data.fixture.json' describe('App displays error messages when user inputs invalid data', async function () { - before(() => { - cy.visit(Cypress.env('signInURL')) - //sign in - cy.signin(userData.cyUserPrimaryEmaiVerified) - }) + //caching user session for each test + const login = (user) => { + cy.session(user.oid, () => { + cy.visit(Cypress.env('signInURL')) + cy.signin(user) + cy.url().should('contain', '/my-orcid') + }) + cy.visit('/my-orcid?orcid=' + user.oid) + } beforeEach(() => { - Cypress.Cookies.preserveOnce('XSRF-TOKEN', 'JSESSIONID') + login(userData.cyUserPrimaryEmaiVerified) }) it('Title is not required', function () { @@ -20,8 +24,8 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input').clear() //empty - cy.get('.url-input') + cy.get('[formcontrolname="description"]').clear() //empty + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateURL) cy.get('#save-websites-button').click({ force: true }) @@ -40,10 +44,10 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.titleURL) - cy.get('.url-input').clear() //empty + cy.get('[formcontrolname="url"]').clear() //empty //try to save cy.get('#save-websites-button').click() @@ -61,10 +65,12 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.titleURL) - cy.get('.url-input').clear().type(testingData.sidebarWebsitesURL.maxSizeURL) + cy.get('[formcontrolname="url"]') + .clear() + .type(testingData.sidebarWebsitesURL.maxSizeURL) //save cy.get('#save-websites-button').click() @@ -83,11 +89,11 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.titleURL) //type a valid url longer than 1999 characters - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.maxSizeURL + '/test') //try to save @@ -107,10 +113,10 @@ describe('App displays error messages when user inputs invalid data', async func }) //add valid title and url cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.titleURL) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateURL) cy.get('#save-websites-button').click() @@ -125,10 +131,10 @@ describe('App displays error messages when user inputs invalid data', async func //try to add same url with all caps cy.get('#add-link').click() cy.get('#draggable-1').within(($row1) => { - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateTitle) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateAllCapsURL) }) @@ -149,7 +155,7 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.noProtocolURL) cy.get('#save-websites-button').click() @@ -168,7 +174,7 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.mixedCaseURL) cy.get('#save-websites-button').click() @@ -191,7 +197,7 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-description-input') .clear() .type(testingData.sidebarWebsitesURL.titleURL) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.internationalURL1) cy.get('#save-websites-button').click() @@ -201,7 +207,7 @@ describe('App displays error messages when user inputs invalid data', async func testingData.errorMessages.invalidURL ) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.internationalURL2) cy.get('#save-websites-button').click() @@ -211,7 +217,7 @@ describe('App displays error messages when user inputs invalid data', async func testingData.errorMessages.invalidURL ) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.internationalURL3) cy.get('#save-websites-button').click() @@ -221,7 +227,7 @@ describe('App displays error messages when user inputs invalid data', async func testingData.errorMessages.invalidURL ) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.internationalURL4) cy.get('#save-websites-button').click() @@ -237,7 +243,7 @@ describe('App displays error messages when user inputs invalid data', async func it('Error messages display according to the language selected', function () { //switch to Spanish cy.get('#cy-language-comp').click() - cy.get('[id^=cdk-overlay]').within(($menuLanguage) => { + cy.get('[role="menu"]').within(($menuLanguage) => { cy.get('button[role="menuitem"]') .contains(testingData.errorMessages.displayLanguageSpanish) .click() @@ -248,10 +254,10 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.titleURL) - cy.get('.url-input').clear() //empty + cy.get('[formcontrolname="url"]').clear() //empty cy.get('#save-websites-button').click() //verify the URL field is required @@ -264,7 +270,7 @@ describe('App displays error messages when user inputs invalid data', async func //switch back to English cy.get('#cy-language-comp').click() - cy.get('[id^=cdk-overlay]').within(($menuLanguage1) => { + cy.get('[role="menu"]').within(($menuLanguage1) => { cy.get('button[role="menuitem"]') .contains(testingData.errorMessages.displayLanguageEnglish) .click() @@ -282,10 +288,10 @@ describe('App displays error messages when user inputs invalid data', async func cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.titleURL) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateURL) cy.get('#save-websites-button').click() @@ -329,9 +335,6 @@ describe('App displays error messages when user inputs invalid data', async func afterEach(() => { //clean up state cy.cleanWebsites() - - //reload page to reflect clean up - cy.reload() }) after(() => { diff --git a/cypress/e2e/my-orcid/sidebar-add-websites.cy.js b/cypress/e2e/my-orcid/sidebar-add-websites.cy.js index 69979b151e..c87e36889f 100644 --- a/cypress/e2e/my-orcid/sidebar-add-websites.cy.js +++ b/cypress/e2e/my-orcid/sidebar-add-websites.cy.js @@ -4,15 +4,18 @@ import userData from '../../fixtures/testing-users.fixture.json' import testingData from '../../fixtures/negative-testing-data.fixture.json' describe('My orcid - users are able to add content to their record', async function () { - before(() => { - cy.visit(Cypress.env('signInURL')) - //sign in - cy.signin(userData.cyUserPrimaryEmaiVerified) - cy.wait(1000) - }) + //caching user session for each test + const login = (user) => { + cy.session(user.oid, () => { + cy.visit(Cypress.env('signInURL')) + cy.signin(user) + cy.url().should('contain', '/my-orcid') + }) + cy.visit('/my-orcid?orcid=' + user.oid) + } beforeEach(() => { - Cypress.Cookies.preserveOnce('XSRF-TOKEN', 'JSESSIONID') + login(userData.cyUserPrimaryEmaiVerified) }) it('User adds a website to their record', function () { @@ -26,8 +29,8 @@ describe('My orcid - users are able to add content to their record', async funct cy.get('#add-link').click() - cy.get('.cy-description-input').clear().type(addDesc) - cy.get('.url-input').clear().type(addUrl) + cy.get('[formcontrolname="description"]').clear().type(addDesc) + cy.get('[formcontrolname="url"]').clear().type(addUrl) cy.get('#save-websites-button').click() cy.wait(1000) @@ -45,10 +48,10 @@ describe('My orcid - users are able to add content to their record', async funct cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.arabicTitle) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateURL) //try to save @@ -68,10 +71,10 @@ describe('My orcid - users are able to add content to their record', async funct cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.russianTitle) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateURL) //try to save @@ -91,10 +94,10 @@ describe('My orcid - users are able to add content to their record', async funct cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.chineseTitle) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateURL) //try to save @@ -114,10 +117,10 @@ describe('My orcid - users are able to add content to their record', async funct cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.titleURL) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(' ' + testingData.sidebarWebsitesURL.duplicateURL) @@ -135,7 +138,7 @@ describe('My orcid - users are able to add content to their record', async funct cy.get('#websites-panel').within(($myPanel) => { cy.get('.cy-edit-button').click() }) - cy.get('.url-input').should('not.contain', ' ') + cy.get('[formcontrolname="url"]').should('not.contain', ' ') }) it('Trailing whitespaces in URL are trimmed on save', function () { @@ -144,10 +147,10 @@ describe('My orcid - users are able to add content to their record', async funct cy.get('.cy-edit-button').click() }) cy.get('#add-link').click() - cy.get('.cy-description-input') + cy.get('[formcontrolname="description"]') .clear() .type(testingData.sidebarWebsitesURL.titleURL) - cy.get('.url-input') + cy.get('[formcontrolname="url"]') .clear() .type(testingData.sidebarWebsitesURL.duplicateURL + ' ') @@ -165,15 +168,13 @@ describe('My orcid - users are able to add content to their record', async funct cy.get('#websites-panel').within(($myPanel) => { cy.get('.cy-edit-button').click() }) - cy.get('.url-input').should('not.contain', ' ') + cy.get('[formcontrolname="url"]').should('not.contain', ' ') + cy.get('#cancel-websites-button').click() }) afterEach(() => { //clean up state cy.cleanWebsites() - - //reload page to reflect clean up - cy.reload() }) after(() => { diff --git a/cypress/e2e/my-orcid/works-visibility-inconsistency-owner-and-clientA.cy.js b/cypress/e2e/my-orcid/works-visibility-inconsistency-owner-and-clientA.cy.js index 7c9ca40365..186b69da99 100644 --- a/cypress/e2e/my-orcid/works-visibility-inconsistency-owner-and-clientA.cy.js +++ b/cypress/e2e/my-orcid/works-visibility-inconsistency-owner-and-clientA.cy.js @@ -84,7 +84,7 @@ describe('My orcid - works - visibility inconsistency notification scenario', as jsonfile['external-ids']['external-id']['external-id-type'] ).click() }) - cy.get('#external-identifier-id-input') + cy.get('[formcontrolname="externalIdentifierId"]') .clear() .type(jsonfile['external-ids']['external-id']['external-id-value']) diff --git a/cypress/e2e/registry/deactivate-reactivate-user.cy.js b/cypress/e2e/registry/deactivate-reactivate-user.cy.js new file mode 100644 index 0000000000..c33777059c --- /dev/null +++ b/cypress/e2e/registry/deactivate-reactivate-user.cy.js @@ -0,0 +1,145 @@ +// +import userData from '../../fixtures/testing-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' + +/* + Deactivation scenarios +*/ + +describe('deactivation process', async function () { + var deactLink = '' + const recordOwner = userData.cyUser_Deactivate_Reactivate + + before(() => {}) + qase( + ['219', '217', '46', '53'], + it('deactivation process - user signed in', function () { + /* + 1. Sign in + 2. go to account settings + 3. request deactivation link + Expect: user receives email in primary email + Expect: confirmation banner is displayed in account settings page + 4. visit deactivation link + Expect: user is taken to /signin + Expect: account is deactivated + */ + + cy.visit('/signin') + cy.url({ timeout: 40000 }).should('include', Cypress.env('signInURL')) + cy.signin(recordOwner) + cy.get('#cy-user-info').click({ force: true }) + //wait for menu overlay to be displayed + cy.wait(4000) + cy.get('#cy-account-settings').click({ force: true }) + cy.url().should('include', '/account') + cy.get('#cy-deactivate-account-panel-action-more').click() + cy.get('#cy-deactivate-account').click() + //cy.wait(4000)//wait to receive email + //use gmail api to check deactvation link was sent + cy.task('checkInbox_from_to_subject', { + options: { + from: Cypress.env('deactivationEmailSender'), + to: recordOwner.email, + subject: Cypress.env('deactivationEmailSubject'), + include_body: true, + }, + }).then((email) => { + assert.isNotNull(email) + const emailBody = email.body.html + //convert string to DOM + const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') + //find the link that points to the correct endpoint + deactLink = htmlDom.querySelector( + `a[href*="account/confirm-deactivate-orcid"]` + ).href + cy.log('link found in email: ' + deactLink) + //verify message banner is displayed + cy.get('#cy-deactivate-account-panel').should( + 'contain', + Cypress.env('deactivationBannerMessage') + ) + cy.visit(deactLink) + cy.url({ timeout: 40000 }).should('include', Cypress.env('signInURL')) + cy.signin(recordOwner) + cy.get('app-deactivated').should( + 'contain', + Cypress.env('deactivatedRecordMessage') + ) + }) + + /* + Reactivating the account + (with a first-name and family-name that already exist on the registry). + Could this be you? modal should not be displayed. + */ + cy.visit('/signin') + cy.url({ timeout: 40000 }).should('include', Cypress.env('signInURL')) + cy.signin(recordOwner) + cy.get('input[formcontrolname="email"]').clear().type(recordOwner.email) + cy.contains('button', 'SUBMIT').click() + + //use gmail api to check reactivatoin link was sent + cy.task('checkInbox_from_to_subject', { + options: { + from: Cypress.env('reactivationEmailSender'), + to: recordOwner.email, + subject: Cypress.env('reactivationEmailSubject'), + include_body: true, + }, + }).then((email) => { + assert.isNotNull(email) + const emailBody = email.body.html + //convert string to DOM + const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') + //find the link that points to the correct endpoint + const href = htmlDom.querySelector( + `a[href*="https://qa.orcid.org/reactivation/"]` + ).href + //follow the link from the email + cy.visit(href) + }) + + cy.url().should('include', Cypress.env('reactivationEmailLink')) + cy.get('#given-names-input').clear().type(recordOwner.name) + cy.get('#family-names-input').clear().type(recordOwner.lastname) + cy.get('#email-input').should('have.value', recordOwner.email) + //step to make sure backend validation on the form is complete + cy.get('app-step-a').within(($appForm) => { + cy.get('form').should('have.class', 'ng-untouched ng-dirty ng-valid') + }) + cy.get('#step-a-next-button').click({ force: true }) + //goes right to step b, "is this you?" modal does not exist in the DOM + cy.get('app-is-this-you').should('not.exist') + cy.get('#password-input').clear().type(recordOwner.password) + cy.get('#password-confirm-input').clear().type(recordOwner.password) + //step to make sure backend validation on the form is complete + cy.get('app-step-b').within(($appForm) => { + cy.get('form').should('have.class', 'ng-untouched ng-dirty ng-valid') + }) + cy.get('#step-b-next').click({ force: true }) + + cy.get('#visibility-everyone-input-input').click({ force: true }) + cy.get('#privacy-input-input').check({ force: true }).should('be.checked') + cy.get('#data-processed-input-input') + .check({ force: true }) + .should('be.checked') + + //CAPTCHA + // Wrap iframe body into a cypress object and perform test within there + cy.getIframeBody('iframe[title="reCAPTCHA"]').within(() => { + cy.get('.recaptcha-checkbox-border').click() + cy.get('#recaptcha-anchor', { timeout: 10000 }).should( + 'have.class', + 'recaptcha-checkbox-checked' + ) + }) + //REACTIVATE button + cy.get('#step-c-register-button').click() + //user taken my orcid page + cy.url({ timeout: 40000 }).should('include', '/my-orcid') + }) + ) //end of qase tag + + after(() => {}) +}) diff --git a/cypress/e2e/registry/deactivation-process.cy.js b/cypress/e2e/registry/deactivation-process.cy.js deleted file mode 100644 index ad4fcd0903..0000000000 --- a/cypress/e2e/registry/deactivation-process.cy.js +++ /dev/null @@ -1,133 +0,0 @@ -// -import userData from '../../fixtures/testing-users.fixture.json' - -/* - Deactivation scenarios -*/ - -describe('deactivation process', async function () { - var deactLink = '' - - before(() => {}) - - it('deactivation process - user signed in', function () { - /* - 1. Sign in - 2. go to account settings - 3. request deactivation link - Expect: user receives email in primary email - Expect: confirmation banner is displayed in account settings page - 4. visit deactivation link - Expect: user is taken to /signin - Expect: account is deactivated - */ - const recordOwner = userData.cyRecordDeactivation1 - - cy.visit('/signin') - cy.url({ timeout: 40000 }).should('include', Cypress.env('signInURL')) - cy.signin(recordOwner) - cy.get('#cy-user-info').click({ force: true }) - //wait for menu overlay to be displayed - cy.wait(4000) - cy.get('#cy-account-settings').click({ force: true }) - cy.url().should('include', '/account') - cy.get('#cy-deactivate-account-panel-action-more').click() - cy.get('#cy-deactivate-account').click() - //cy.wait(4000)//wait to receive email - //use gmail api to check deactvation link was sent - cy.task('checkInbox_from_to_subject', { - options: { - from: Cypress.env('deactivationEmailSender'), - to: recordOwner.email, - subject: Cypress.env('deactivationEmailSubject'), - include_body: true, - }, - }).then((email) => { - assert.isNotNull(email) - const emailBody = email.body.html - //convert string to DOM - const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') - //find the link that points to the correct endpoint - deactLink = htmlDom.querySelector( - `a[href*="account/confirm-deactivate-orcid"]` - ).href - cy.log('link found in email: ' + deactLink) - //verify message banner is displayed - cy.get('#cy-deactivate-account-panel').should( - 'contain', - Cypress.env('deactivationBannerMessage') - ) - cy.visit(deactLink) - cy.url({ timeout: 40000 }).should('include', Cypress.env('signInURL')) - cy.signin(recordOwner) - cy.get('app-deactivated').should( - 'contain', - Cypress.env('deactivatedRecordMessage') - ) - }) - }) - - it.skip('deactivation process - user signed out', function () { - /* - 1. Sign in - 2. go to account settings - 3. request deactivation link - Expect: user receives email in primary email - Expect: confirmation banner is displayed in account settings page - 4. Sign out - 5. visit deactivation link - Expect: user is taken to /signin - Expect: account is deactivated - */ - const recordOwner = userData.cyRecordDeactivation2 - - cy.visit('/signin') - cy.url({ timeout: 40000 }).should('include', Cypress.env('signInURL')) - cy.signin(recordOwner) - cy.get('#cy-user-info').click({ force: true }) - //wait for menu overlay to be displayed - cy.wait(4000) - cy.get('#cy-account-settings').click({ force: true }) - cy.url().should('include', '/account') - cy.get('#cy-deactivate-account-panel-action-more').click() - cy.get('#cy-deactivate-account').click() - //use gmail api to check deactvation link was sent - cy.task('checkInbox_from_to_subject', { - options: { - from: Cypress.env('deactivationEmailSender'), - to: recordOwner.email, - subject: Cypress.env('deactivationEmailSubject'), - include_body: true, - }, - }).then((email) => { - assert.isNotNull(email) - const emailBody = email.body.html - //convert string to DOM - const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') - //find the link that points to the correct endpoint - deactLink = htmlDom.querySelector( - `a[href*="account/confirm-deactivate-orcid"]` - ).href - cy.log('link found in email: ' + deactLink) - //verify message banner is displayed - cy.get('#cy-deactivate-account-panel').should( - 'contain', - Cypress.env('deactivationBannerMessage') - ) - - //SIGN OUT - cy.get('#cy-user-info').click() - cy.get('#cy-signout').click({ force: true }) - - cy.visit(deactLink) - cy.url({ timeout: 40000 }).should('include', Cypress.env('signInURL')) - cy.signin(recordOwner) - cy.get('app-deactivated').should( - 'contain', - Cypress.env('deactivatedRecordMessage') - ) - }) - }) - - after(() => {}) -}) diff --git a/cypress/e2e/registry/email-verification-reminder.cy.js b/cypress/e2e/registry/email-verification-reminder.cy.js index bdcf5223b7..d2dc297d24 100644 --- a/cypress/e2e/registry/email-verification-reminder.cy.js +++ b/cypress/e2e/registry/email-verification-reminder.cy.js @@ -1,6 +1,7 @@ /// import userData from '../../fixtures/testing-users.fixture.json' +import { qase } from 'cypress-qase-reporter/dist/mocha' describe('Primary account email verification reminders', async function () { beforeEach(() => { @@ -10,50 +11,55 @@ describe('Primary account email verification reminders', async function () { /* registered user has not verified primary account email, gets reminder in scope: check the user gets the reminder email with correct link out of scope: this test case will not click on the link so states remains the same */ - it('Receive reminder email if primary account email has not been verified', function () { - //click Sign in - cy.get('#menu-signin-button').click() - - //sign in with registered user - cy.log( - 'Signing in with user: ' + userData.cyUserPrimaryEmailNotVerified.oid - ) - cy.get('#username').clear().type(userData.cyUserPrimaryEmailNotVerified.oid) - cy.get('#password') - .clear() - .type(userData.cyUserPrimaryEmailNotVerified.password) - cy.get('#signin-button').click() - - //request to get reminder email to verify primary account email - cy.get('#biography-panel').within(($myPanel) => { - cy.get('.cy-edit-button').click() - }) - cy.contains('Resend verification email').click() - - //use gmail api to check verification email was sent - cy.task('checkInbox_from_to_subject', { - options: { - from: Cypress.env('senderVerifyEmail'), - to: userData.cyUserPrimaryEmailNotVerified.email, - subject: Cypress.env('verifyEmailReminderSubject'), - include_body: true, - }, - }).then((email) => { - assert.isNotNull(email) - const emailBody = email.body.html - cy.log('>>>>>>>>>Email body is: ' + JSON.stringify(email.body)) - - //convert string to DOM - const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') - - //href points to correct endpoint - cy.get(htmlDom.querySelector('[id$="verificationButton"]')) - .invoke('attr', 'href') - .should('include', Cypress.env('verifyEmailUrl')) - }) + qase( + '106', + it('Receive reminder email if primary account email has not been verified', function () { + //click Sign in + cy.get('#menu-signin-button').click() - //log out - cy.get('#cy-user-info').click() - cy.get('#cy-signout').click({ force: true }) - }) + //sign in with registered user + cy.log( + 'Signing in with user: ' + userData.cyUserPrimaryEmailNotVerified.oid + ) + cy.get('#username') + .clear() + .type(userData.cyUserPrimaryEmailNotVerified.oid) + cy.get('#password') + .clear() + .type(userData.cyUserPrimaryEmailNotVerified.password) + cy.get('#signin-button').click() + + //request to get reminder email to verify primary account email + cy.get('#biography-panel').within(($myPanel) => { + cy.get('.cy-edit-button').click() + }) + cy.contains('Resend verification email').click() + + //use gmail api to check verification email was sent + cy.task('checkInbox_from_to_subject', { + options: { + from: Cypress.env('senderVerifyEmail'), + to: userData.cyUserPrimaryEmailNotVerified.email, + subject: Cypress.env('verifyEmailReminderSubject'), + include_body: true, + }, + }).then((email) => { + assert.isNotNull(email) + const emailBody = email.body.html + cy.log('>>>>>>>>>Email body is: ' + JSON.stringify(email.body)) + + //convert string to DOM + const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') + + //href points to correct endpoint + cy.get(htmlDom.querySelector('[id$="verificationButton"]')) + .invoke('attr', 'href') + .should('include', Cypress.env('verifyEmailUrl')) + }) + + //log out + cy.get('#cy-user-info').click() + cy.get('#cy-signout').click({ force: true }) + }) + ) //end of qase tag }) diff --git a/cypress/e2e/registry/new-user-registration.cy.js b/cypress/e2e/registry/new-user-registration.cy.js index fde031f04e..87ce2e9c43 100644 --- a/cypress/e2e/registry/new-user-registration.cy.js +++ b/cypress/e2e/registry/new-user-registration.cy.js @@ -1,102 +1,106 @@ /// const randomUser = require('../../helpers/randomUser') +import { qase } from 'cypress-qase-reporter/dist/mocha' describe('Register new user', async function () { beforeEach(() => { cy.visit('/') }) - it('New user registers and verifies primary email', function () { - //Bypass the duplicated research call to avoid getting the "Is this you modal" - cy.intercept('GET', Cypress.env('duplicatedModalEndPoint'), []) + qase( + ['104', '161'], + it('New user registers and verifies primary email', function () { + //Bypass the duplicated research call to avoid getting the "Is this you modal" + cy.intercept('GET', Cypress.env('duplicatedModalEndPoint'), []) - //generate a new (random) user - const userToRegister = randomUser() - //convert email to lower case as gmail uses it this way - userToRegister.email = userToRegister.email.toLowerCase() + //generate a new (random) user + const userToRegister = randomUser() + //convert email to lower case as gmail uses it this way + userToRegister.email = userToRegister.email.toLowerCase() - cy.get('#menu-signin-button').click() - //verify user is redirected to the sign in page - cy.url().should('include', '/signin') - cy.get('#register-button').click() - //verify user is redirected to the register page - cy.url().should('include', '/register') + cy.get('#menu-signin-button').click() + //verify user is redirected to the sign in page + cy.url().should('include', '/signin') + cy.get('#register-button').click() + //verify user is redirected to the register page + cy.url().should('include', '/register') - //STEP 1/3 populate form - cy.get('#given-names-input').clear().type(userToRegister.name) - cy.get('#family-names-input').clear().type(userToRegister.familyName) - cy.get('#email-input').clear().type(userToRegister.email) - cy.get('#confirm-email-input').clear().type(userToRegister.email) - cy.get('#step-a-next-button').click() + //STEP 1/3 populate form + cy.get('#given-names-input').clear().type(userToRegister.name) + cy.get('#family-names-input').clear().type(userToRegister.familyName) + cy.get('#email-input').clear().type(userToRegister.email) + cy.get('#confirm-email-input').clear().type(userToRegister.email) + cy.get('#step-a-next-button').click() - //STEP 2/3 create OID - cy.get('.mat-card-title').contains('Create your ORCID iD') - cy.get('#password-input').clear().type(userToRegister.password) - cy.get('#password-confirm-input').clear().type(userToRegister.password) - cy.get('.ng-valid #step-b-next').click() + //STEP 2/3 create OID + cy.get('.mat-card-title').contains('Create your ORCID iD') + cy.get('#password-input').clear().type(userToRegister.password) + cy.get('#password-confirm-input').clear().type(userToRegister.password) + cy.get('.ng-valid #step-b-next').click() - //STEP 3/3 - cy.get('#visibility-everyone-input-input').click({ force: true }) - cy.get('#privacy-input-input').check({ force: true }).should('be.checked') - cy.get('#data-processed-input-input') - .check({ force: true }) - .should('be.checked') + //STEP 3/3 + cy.get('#visibility-everyone-input-input').click({ force: true }) + cy.get('#privacy-input-input').check({ force: true }).should('be.checked') + cy.get('#data-processed-input-input') + .check({ force: true }) + .should('be.checked') - //CAPTCHA - // Wrap iframe body into a cypress object and perform test within there - cy.getIframeBody('iframe[title="reCAPTCHA"]').within(() => { - cy.get('.recaptcha-checkbox-border').click() - cy.get('#recaptcha-anchor', { timeout: 10000 }).should( - 'have.class', - 'recaptcha-checkbox-checked' - ) - }) + //CAPTCHA + // Wrap iframe body into a cypress object and perform test within there + cy.getIframeBody('iframe[title="reCAPTCHA"]').within(() => { + cy.get('.recaptcha-checkbox-border').click() + cy.get('#recaptcha-anchor', { timeout: 10000 }).should( + 'have.class', + 'recaptcha-checkbox-checked' + ) + }) - cy.get('#step-c-register-button').click() + cy.get('#step-c-register-button').click() - //use Gmail API to check verification email was sent - //this task will poll the inbox every 15sec to check for the email - cy.task('checkInbox_from_to_subject', { - options: { - from: Cypress.env('senderVerifyEmail'), - to: userToRegister.email, - subject: Cypress.env('verifyEmailSubject'), - include_body: true, - }, - }).then((email) => { - assert.isNotNull(email) - const emailBody = email.body.html - cy.log('>>>>>>>>>Email body is: ' + JSON.stringify(email.body)) + //use Gmail API to check verification email was sent + //this task will poll the inbox every 15sec to check for the email + cy.task('checkInbox_from_to_subject', { + options: { + from: Cypress.env('senderVerifyEmail'), + to: userToRegister.email, + subject: Cypress.env('verifyEmailSubject'), + include_body: true, + }, + }).then((email) => { + assert.isNotNull(email) + const emailBody = email.body.html + cy.log('>>>>>>>>>Email body is: ' + JSON.stringify(email.body)) - //convert string to DOM - const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') - cy.log(htmlDom.documentElement.innerHTML) - //find the link - const href = htmlDom.querySelector('[id$="verificationButton"]').href - cy.log('>>>>>>>found the link: ' + href) + //convert string to DOM + const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') + cy.log(htmlDom.documentElement.innerHTML) + //find the link + const href = htmlDom.querySelector('[id$="verificationButton"]').href + cy.log('>>>>>>>found the link: ' + href) - // make an api request for this resource (no UI needed) - // drill into the response to check it was successful - cy.request(href).its('status').should('eq', 200) - }) + // make an api request for this resource (no UI needed) + // drill into the response to check it was successful + cy.request(href).its('status').should('eq', 200) + }) - //reload page - cy.reload() - cy.wait(4000) //REMOVE after fix by dev - //try editing Bio which only users with verified emails can do - cy.get('#biography-panel').within(($bioSection) => { - cy.get('.cy-edit-button').click() - }) - cy.get('#biography-input') - .clear() - .type('only users with verified emails can edit bio') - cy.get('#save-biography-button').click() - cy.get('#biography').contains( - 'only users with verified emails can edit bio' - ) + //reload page + cy.reload() + cy.wait(4000) //REMOVE after fix by dev + //try editing Bio which only users with verified emails can do + cy.get('#biography-panel').within(($bioSection) => { + cy.get('.cy-edit-button').click() + }) + cy.get('#biography-input') + .clear() + .type('only users with verified emails can edit bio') + cy.get('#save-biography-button').click() + cy.get('#biography').contains( + 'only users with verified emails can edit bio' + ) - //log out - cy.get('#cy-user-info').click() - cy.get('#cy-signout').click({ force: true }) - }) + //log out + cy.get('#cy-user-info').click() + cy.get('#cy-signout').click({ force: true }) + }) + ) //end of qase tag }) diff --git a/cypress/e2e/registry/reactivation-existing-names.cy.js b/cypress/e2e/registry/reactivation-existing-names.cy.js deleted file mode 100644 index bde1e30153..0000000000 --- a/cypress/e2e/registry/reactivation-existing-names.cy.js +++ /dev/null @@ -1,83 +0,0 @@ -// -import userData from '../../fixtures/testing-users.fixture.json' - -/* -Reactivate your account with a first-name and family-name that already exist on the registry. -Could this be you? modal should not be displayed. -*/ - -describe('reactivation process for deactivated account', async function () { - const recordOwner = userData.cyRecordReactivate - - before(() => {}) - - it('reactivation process for deactivated account', function () { - cy.visit('/signin') - cy.url({ timeout: 40000 }).should('include', Cypress.env('signInURL')) - cy.signin(recordOwner) - cy.get('input[formcontrolname="email"]').clear().type(recordOwner.email) - cy.contains('button', 'SUBMIT').click() - - //use gmail api to check reactivatoin link was sent - cy.task('checkInbox_from_to_subject', { - options: { - from: Cypress.env('reactivationEmailSender'), - to: recordOwner.email, - subject: Cypress.env('reactivationEmailSubject'), - include_body: true, - }, - }).then((email) => { - assert.isNotNull(email) - const emailBody = email.body.html - //convert string to DOM - const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') - //find the link that points to the correct endpoint - const href = htmlDom.querySelector( - `a[href*="https://qa.orcid.org/reactivation/"]` - ).href - //follow the link from the email - cy.visit(href) - }) - - cy.url().should('include', Cypress.env('reactivationEmailLink')) - cy.get('#given-names-input').clear().type(recordOwner.name) - cy.get('#family-names-input').clear().type(recordOwner.lastname) - cy.get('#email-input').should('have.value', recordOwner.email) - //step to make sure backend validation on the form is complete - cy.get('app-step-a').within(($appForm) => { - cy.get('form').should('have.class', 'ng-untouched ng-dirty ng-valid') - }) - cy.get('#step-a-next-button').click({ force: true }) - //goes right to step b, "is this you?" modal does not exist in the DOM - cy.get('app-is-this-you').should('not.exist') - cy.get('#password-input').clear().type(recordOwner.password) - cy.get('#password-confirm-input').clear().type(recordOwner.password) - //step to make sure backend validation on the form is complete - cy.get('app-step-b').within(($appForm) => { - cy.get('form').should('have.class', 'ng-untouched ng-dirty ng-valid') - }) - cy.get('#step-b-next').click({ force: true }) - - cy.get('#visibility-everyone-input-input').click({ force: true }) - cy.get('#privacy-input-input').check({ force: true }).should('be.checked') - cy.get('#data-processed-input-input') - .check({ force: true }) - .should('be.checked') - - //CAPTCHA - // Wrap iframe body into a cypress object and perform test within there - cy.getIframeBody('iframe[title="reCAPTCHA"]').within(() => { - cy.get('.recaptcha-checkbox-border').click() - cy.get('#recaptcha-anchor', { timeout: 10000 }).should( - 'have.class', - 'recaptcha-checkbox-checked' - ) - }) - //REACTIVATE button - cy.get('#step-c-register-button').click() - //user taken my orcid page - cy.url({ timeout: 40000 }).should('include', '/my-orcid') - }) - - after(() => {}) -}) diff --git a/cypress/e2e/registry/reset-password-recover-id.cy.js b/cypress/e2e/registry/reset-password-recover-id.cy.js index 1159a6757c..1ec310d88c 100644 --- a/cypress/e2e/registry/reset-password-recover-id.cy.js +++ b/cypress/e2e/registry/reset-password-recover-id.cy.js @@ -20,6 +20,7 @@ describe('Password reset and OID recovery', () => { .type(userData.cyResetPasswordUser.email) //click button to recover details cy.get('#cy-recover-acc-details').click() + cy.wait(2000) //use gmail api to check recovery email was sent cy.task('checkInbox_from_to_subject', { @@ -42,11 +43,14 @@ describe('Password reset and OID recovery', () => { }) cy.wait(3000) //type new passw + cy.contains('There is a problem with your reset password link.').should( + 'not.exist' + ) cy.get('#cy-password-input').clear().type(newPassword) //confirm new passw cy.get('#cy-password-confirm-input').clear().type(newPassword) //save - cy.wait(2000) //need to wait for button to become enabled + //cy.wait(2000) cy.get('#cy-save-password').should('be.enabled').click({ force: true }) //verify user is redirected to Sign in page diff --git a/cypress/e2e/registry/thank-you-for-registering-banner.cy.js b/cypress/e2e/registry/thank-you-for-registering-banner.cy.js new file mode 100644 index 0000000000..0932b1d327 --- /dev/null +++ b/cypress/e2e/registry/thank-you-for-registering-banner.cy.js @@ -0,0 +1,120 @@ +/// +const randomUser = require('../../helpers/randomUser') +import { qase } from 'cypress-qase-reporter/dist/mocha' + +describe('Register new user - a Thank you banner is displayed - verify email sent', async function () { + const welcomeMessage = 'Thank you for registering with ORCID' + const emailVerifRemidnerMessage = 'Please verify your primary email address!' + + beforeEach(() => { + cy.visit('/') + }) + + qase( + ['105'], + it('Register new user - a Thank you banner is displayed - verify email sent', function () { + //Bypass the duplicated research call to avoid getting the "Is this you modal" + cy.intercept('GET', Cypress.env('duplicatedModalEndPoint'), []) + + //generate a new (random) user + const userToRegister = randomUser() + //convert email to lower case as gmail uses it this way + userToRegister.email = userToRegister.email.toLowerCase() + + cy.get('#menu-signin-button').click() + //verify user is redirected to the sign in page + cy.url().should('include', '/signin') + cy.get('#register-button').click() + //verify user is redirected to the register page + cy.url().should('include', '/register') + + //STEP 1/3 populate form + cy.get('#given-names-input').clear().type(userToRegister.name) + cy.get('#family-names-input').clear().type(userToRegister.familyName) + cy.get('#email-input').clear().type(userToRegister.email) + cy.get('#confirm-email-input').clear().type(userToRegister.email) + cy.get('#step-a-next-button').click() + + //STEP 2/3 create OID + cy.get('.mat-card-title').contains('Create your ORCID iD') + cy.get('#password-input').clear().type(userToRegister.password) + cy.get('#password-confirm-input').clear().type(userToRegister.password) + cy.get('.ng-valid #step-b-next').click() + + //STEP 3/3 + cy.get('#visibility-everyone-input-input').click({ force: true }) + cy.get('#privacy-input-input').check({ force: true }).should('be.checked') + cy.get('#data-processed-input-input') + .check({ force: true }) + .should('be.checked') + + //CAPTCHA + // Wrap iframe body into a cypress object and perform test within there + cy.getIframeBody('iframe[title="reCAPTCHA"]').within(() => { + cy.get('.recaptcha-checkbox-border').click() + cy.get('#recaptcha-anchor', { timeout: 10000 }).should( + 'have.class', + 'recaptcha-checkbox-checked' + ) + }) + cy.get('#step-c-register-button').click() + + //verify top banner + cy.contains('app-top-bar-verification-email', welcomeMessage).should( + 'be.visible' + ) + cy.wait(4000) //wait for back end so button is clickable + cy.contains('button', 'Resend verification email').click({ force: true }) + cy.wait(4000) //wait for banner to dinamically update message + cy.contains( + 'app-top-bar-verification-email', + emailVerifRemidnerMessage + ).should('be.visible') + + //use Gmail API to check verification email was sent + //this task will poll the inbox every 15sec to check for the email + cy.task('checkInbox_from_to_subject', { + options: { + from: Cypress.env('senderVerifyEmail'), + to: userToRegister.email, + subject: Cypress.env('verifyEmailReminderSubject'), + include_body: true, + }, + }).then((email) => { + assert.isNotNull(email) + const emailBody = email.body.html + cy.log('>>>>>>>>>Email body is: ' + JSON.stringify(email.body)) + + //convert string to DOM + const htmlDom = new DOMParser().parseFromString(emailBody, 'text/html') + cy.log(htmlDom.documentElement.innerHTML) + //find the link + const href = htmlDom.querySelector('[id$="verificationButton"]').href + cy.log('>>>>>>>found the link: ' + href) + + // make an api request for this resource (no UI needed) + // drill into the response to check it was successful + cy.request(href).its('status').should('eq', 200) + }) + + //reload page + cy.reload() + cy.wait(4000) + //try editing Bio which only users with verified emails can do + cy.get('#biography-panel').within(($bioSection) => { + cy.get('.cy-edit-button').click() + }) + cy.get('#biography-input') + .clear() + .type('only users with verified emails can edit bio') + cy.get('#save-biography-button').click() + cy.get('#biography').contains( + 'only users with verified emails can edit bio' + ) + + //log out + cy.get('#cy-user-info').click() + cy.get('#cy-signout').click({ force: true }) + }) + ) //end of qase tag +}) diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 654bc9ac40..456e65135f 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -7,3 +7,4 @@ import './signin.commands' import './clean.commands' import './create.commands' import 'cypress-mochawesome-reporter/register' +import 'cypress-file-upload' diff --git a/package.json b/package.json index 2ac5a8b5b3..4d680e635b 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.0.0", "scripts": { "start": "ng serve --disable-host-check --host 0.0.0.0", + "start-qa": "ng serve --configuration=local-qa --host 0.0.0.0", + "start-sandbox": "ng serve --configuration=local-sandbox --host 0.0.0.0", + "start:en": "ng serve --configuration=local,en --disable-host-check", "start:fr": "ng serve --configuration=local,fr --disable-host-check", "start:ar": "ng serve --configuration=local,ar --disable-host-check", @@ -69,10 +72,10 @@ "@angular/router": "^14.2.9", "@angular/service-worker": "^14.2.9", "@orcid/bibtex-parse-js": "0.0.25", - "@types/gtag.js": "^0.0.3", "browserslist": "^4.19.1", "browserslist-useragent-regexp": "^3.0.0", "core-js": "^3.6.5", + "cypress-file-upload": "^5.0.8", "del": "^6.0.0", "dotenv": "^16.0.3", "firebase": "^7.6.0", @@ -107,7 +110,7 @@ "@types/xml2js": "^0.4.4", "axe-core": "^4.0.2", "cross-env": "^5.2.1", - "cypress": "^10.10.0", + "cypress": "12.7.0", "cypress-mochawesome-reporter": "^3.2.0", "cypress-multi-reporters": "^1.6.2", "cypress-otp": "^1.0.3", diff --git a/scripts/google-analytics.postbuild.ts b/scripts/google-analytics.postbuild.ts deleted file mode 100644 index 28393b21ad..0000000000 --- a/scripts/google-analytics.postbuild.ts +++ /dev/null @@ -1,16 +0,0 @@ -export function googleAnalytics(indexHtml: string, options) { - if ( - options.environmentVariables && - options.environmentVariables.GOOGLE_ANALYTICS - ) { - return indexHtml.replace( - /UA-0000000-00/g, - options.environmentVariables.GOOGLE_ANALYTICS - ) - } else { - console.warn( - 'This build will not have google analytics since the UID is not defined on the environment variables' - ) - return indexHtml - } -} diff --git a/scripts/lighthouse/firebase.ts b/scripts/lighthouse/firebase.ts index 6346bab0e2..7d82ad5ea6 100644 --- a/scripts/lighthouse/firebase.ts +++ b/scripts/lighthouse/firebase.ts @@ -41,9 +41,9 @@ export class FirebaseManager { // Without auth 'no-auth' - const id = `${ - result.auditDefinition.url || 'root' - }.${user}.${date.toISOString().replace(/:/g, '.')}` + const id = `${result.auditDefinition.url || 'root'}.${user}.${date + .toISOString() + .replace(/:/g, '.')}` return id } diff --git a/scripts/postbuild.ts b/scripts/postbuild.ts index 405372f73b..c7ac6c734b 100644 --- a/scripts/postbuild.ts +++ b/scripts/postbuild.ts @@ -2,7 +2,6 @@ import { uniqueLength } from './unique-length.postbuild' import { buildInfo } from './build-info.postbuild' -import { googleAnalytics } from './google-analytics.postbuild' import { addLanguageCodeToHashesOnToHTMLFiles, addLanguageCodeToHashesOnJSFiles, @@ -21,7 +20,6 @@ glob let data = readFileSync(file, 'utf8') data = uniqueLength(data, options) data = buildInfo(data, options) - data = googleAnalytics(data, options) data = zendeskPlugin(data, options) // Replace all the `*.js` references to match updated JS file names with the language code. data = addLanguageCodeToHashesOnToHTMLFiles(data, options) diff --git a/scripts/properties-folder-manager.ts b/scripts/properties-folder-manager.ts index ed03c89224..616bed3aeb 100644 --- a/scripts/properties-folder-manager.ts +++ b/scripts/properties-folder-manager.ts @@ -92,9 +92,8 @@ abstract class PropertyFolderImpl implements PropertyFolder { */ cloneValues(originFolder: PropertyFolderImpl): PropertyFolder { console.debug('Clone values from properties ________') - const matchingProperties: MatchingPair[] = this.matchingValueEnglishProperties( - originFolder - ) + const matchingProperties: MatchingPair[] = + this.matchingValueEnglishProperties(originFolder) matchingProperties.forEach((matching) => { PropertyFolderImpl.supportedLanguagesFile.forEach((language) => { if (language !== 'en') { diff --git a/src/app/account-settings/components/settings-defaults-email-frequency/settings-defaults-email-frequency.component.ts b/src/app/account-settings/components/settings-defaults-email-frequency/settings-defaults-email-frequency.component.ts index c0eeaff19f..4cc86ef71c 100644 --- a/src/app/account-settings/components/settings-defaults-email-frequency/settings-defaults-email-frequency.component.ts +++ b/src/app/account-settings/components/settings-defaults-email-frequency/settings-defaults-email-frequency.component.ts @@ -25,7 +25,8 @@ import { preserveWhitespaces: true, }) export class SettingsDefaultsEmailFrequencyComponent - implements OnInit, OnDestroy { + implements OnInit, OnDestroy +{ $destroy: Subject = new Subject() isMobile: boolean frequencyOption = [ diff --git a/src/app/account-settings/components/settings-security-alternate-sign-in/settings-security-alternate-sign-in.component.ts b/src/app/account-settings/components/settings-security-alternate-sign-in/settings-security-alternate-sign-in.component.ts index 6a428ab8c2..f168ad1e0e 100644 --- a/src/app/account-settings/components/settings-security-alternate-sign-in/settings-security-alternate-sign-in.component.ts +++ b/src/app/account-settings/components/settings-security-alternate-sign-in/settings-security-alternate-sign-in.component.ts @@ -20,7 +20,8 @@ import { DialogSecurityAlternateAccountDeleteComponent } from '../dialog-securit styleUrls: ['./settings-security-alternate-sign-in.component.scss'], }) export class SettingsSecurityAlternateSignInComponent - implements OnInit, OnDestroy { + implements OnInit, OnDestroy +{ @Output() loading = new EventEmitter() accounts$: Observable diff --git a/src/app/account-trusted-parties/components/dialog-add-trusted-individuals-your-own-email/dialog-add-trusted-individuals-your-own-email.component.ts b/src/app/account-trusted-parties/components/dialog-add-trusted-individuals-your-own-email/dialog-add-trusted-individuals-your-own-email.component.ts index 1a6bfbac75..16916af4ba 100644 --- a/src/app/account-trusted-parties/components/dialog-add-trusted-individuals-your-own-email/dialog-add-trusted-individuals-your-own-email.component.ts +++ b/src/app/account-trusted-parties/components/dialog-add-trusted-individuals-your-own-email/dialog-add-trusted-individuals-your-own-email.component.ts @@ -10,7 +10,8 @@ import { environment } from 'src/environments/environment' styleUrls: ['./dialog-add-trusted-individuals-your-own-email.component.scss'], }) export class DialogAddTrustedIndividualsYourOwnEmailComponent - implements OnInit { + implements OnInit +{ constructor( private matRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: ExpandedSearchResultsContent | string diff --git a/src/app/account-trusted-parties/components/settings-trusted-individuals-search/settings-trusted-individuals-search.component.html b/src/app/account-trusted-parties/components/settings-trusted-individuals-search/settings-trusted-individuals-search.component.html index 2fb8820c2d..cf37046599 100644 --- a/src/app/account-trusted-parties/components/settings-trusted-individuals-search/settings-trusted-individuals-search.component.html +++ b/src/app/account-trusted-parties/components/settings-trusted-individuals-search/settings-trusted-individuals-search.component.html @@ -162,7 +162,7 @@ [length]="searchResults['num-found']" (page)="changePage($event)" [attr.aria-label]="ariaLabelPaginator" - role="navigation" + role="status" > diff --git a/src/app/account-trusted-parties/components/settings-trusted-individuals-search/settings-trusted-individuals-search.component.ts b/src/app/account-trusted-parties/components/settings-trusted-individuals-search/settings-trusted-individuals-search.component.ts index 5988f11f43..a6600b5cc8 100644 --- a/src/app/account-trusted-parties/components/settings-trusted-individuals-search/settings-trusted-individuals-search.component.ts +++ b/src/app/account-trusted-parties/components/settings-trusted-individuals-search/settings-trusted-individuals-search.component.ts @@ -1,6 +1,7 @@ +import { LiveAnnouncer } from '@angular/cdk/a11y' import { Component, OnDestroy, OnInit } from '@angular/core' import { MatDialog } from '@angular/material/dialog' -import { PageEvent } from '@angular/material/paginator' +import { MatPaginatorIntl, PageEvent } from '@angular/material/paginator' import { Observable, of, Subject } from 'rxjs' import { map, @@ -18,6 +19,7 @@ import { } from 'src/app/constants' import { UserService } from 'src/app/core' import { AccountTrustedIndividualsService } from 'src/app/core/account-trusted-individuals/account-trusted-individuals.service' +import { AnnouncerService } from 'src/app/core/announcer/announcer.service' import { SearchService } from 'src/app/core/search/search.service' import { ExpandedSearchResultsContent, SearchResults } from 'src/app/types' import { AccountTrustedIndividual } from 'src/app/types/account-trusted-individuals' @@ -35,7 +37,8 @@ import { DialogAddTrustedIndividualsComponent } from '../dialog-add-trusted-indi ], }) export class SettingsTrustedIndividualsSearchComponent - implements OnInit, OnDestroy { + implements OnInit, OnDestroy +{ $destroy = new Subject() searchDone = false displayedColumns = ['trustedIndividuals', 'orcid', 'actions'] @@ -53,13 +56,17 @@ export class SettingsTrustedIndividualsSearchComponent searchResultsByName: boolean alreadyAddedLabel = $localize`:@@account.alreadyAdded:You already added this user` trustedPartiesUrl = '/trusted-parties' + paginatorLabel: any + trustedIndividualsLabel = $localize`:@@account.trustedIndividuals:Trusted individuals` constructor( private _search: SearchService, private dialog: MatDialog, private _platform: PlatformInfoService, private account: AccountTrustedIndividualsService, - private _user: UserService + private _user: UserService, + private _announcer: AnnouncerService, + private _matPaginatorIntl: MatPaginatorIntl ) {} search(value: string) { @@ -236,6 +243,12 @@ export class SettingsTrustedIndividualsSearchComponent changePage(event: PageEvent) { this.pageIndex = event.pageIndex this.pageSize = event.pageSize + this.paginatorLabel = this._matPaginatorIntl.getRangeLabel( + event.pageIndex, + event.pageSize, + event.length + ) + this._announcer.liveAnnouncePagination(event, this.trustedIndividualsLabel) this.search(this.searchValue) } ngOnInit(): void { diff --git a/src/app/account-trusted-parties/components/settings-trusted-individuals/settings-trusted-individuals.component.ts b/src/app/account-trusted-parties/components/settings-trusted-individuals/settings-trusted-individuals.component.ts index c6c86d9f3f..c9e8fdae9a 100644 --- a/src/app/account-trusted-parties/components/settings-trusted-individuals/settings-trusted-individuals.component.ts +++ b/src/app/account-trusted-parties/components/settings-trusted-individuals/settings-trusted-individuals.component.ts @@ -45,10 +45,11 @@ export class SettingsTrustedIndividualsComponent implements OnInit, OnDestroy { } private setupTrustedIndividualsObs() { - this.$trustedIndividuals = this._trustedIndividualsService.updateTrustedIndividualsSuccess.pipe( - startWith(() => undefined), - switchMap(() => this._trustedIndividualsService.get()) - ) + this.$trustedIndividuals = + this._trustedIndividualsService.updateTrustedIndividualsSuccess.pipe( + startWith(() => undefined), + switchMap(() => this._trustedIndividualsService.get()) + ) } revokeAccess(accountTrustedOrganization: AccountTrustedIndividual) { diff --git a/src/app/account-trusted-parties/components/settings-users-that-thrust-you/settings-users-that-thrust-you.component.ts b/src/app/account-trusted-parties/components/settings-users-that-thrust-you/settings-users-that-thrust-you.component.ts index e797b2a641..43c5ce5625 100644 --- a/src/app/account-trusted-parties/components/settings-users-that-thrust-you/settings-users-that-thrust-you.component.ts +++ b/src/app/account-trusted-parties/components/settings-users-that-thrust-you/settings-users-that-thrust-you.component.ts @@ -35,6 +35,7 @@ export class SettingsUsersThatThrustYouComponent implements OnInit { } private setupTrustedIndividualsObs() { - this.$usersThatThrustYou = this._trustedIndividualsService.getTrustedIndividuals() + this.$usersThatThrustYou = + this._trustedIndividualsService.getTrustedIndividuals() } } diff --git a/src/app/analytics-utils.ts b/src/app/analytics-utils.ts new file mode 100644 index 0000000000..85b002728f --- /dev/null +++ b/src/app/analytics-utils.ts @@ -0,0 +1,65 @@ +import { RequestInfoForm } from './types' +import { PerformanceMarks } from './constants' + +export const browserGlobals = { + windowRef(): any { + return window + }, + documentRef(): any { + return document + }, +} + +export function reportNavigationStart(url: string) { + startPerformanceMeasurement(url) +} + +export function removeUrlParameters(url: string) { + return url.split('?')[0] +} + +export function startPerformanceMeasurement(url: string): void { + if (window.performance) { + window.performance.mark(PerformanceMarks.navigationStartPrefix + url) + } +} + +export function finishPerformanceMeasurement(url: string): number | void { + if (window.performance) { + window.performance.mark(PerformanceMarks.navigationEndPrefix + url) + let timeForNavigation + window.performance.measure( + url, + PerformanceMarks.navigationStartPrefix + url, + PerformanceMarks.navigationEndPrefix + url + ) + window.performance.getEntriesByName(url).forEach((value) => { + timeForNavigation = value.duration + }) + clearPerformanceMarks(url) + return timeForNavigation + } +} + +export function clearPerformanceMarks(url: string) { + if (window.performance) { + window.performance.clearMarks(PerformanceMarks.navigationStartPrefix + url) + window.performance.clearMarks(PerformanceMarks.navigationEndPrefix + url) + window.performance.clearMeasures(url) + } +} + +export function buildClientString(request: RequestInfoForm) { + return request.memberName + ' - ' + request.clientName +} + +export function getDataLayer(): any[] { + const window = browserGlobals.windowRef() + window.dataLayer = window.dataLayer || [] + return window.dataLayer +} + +export function pushOnDataLayer(object: Object): void { + const dataLayer = getDataLayer() + dataLayer.push(object) +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0be64ed07f..ea4f6813d2 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,14 +1,21 @@ import { Component, HostBinding, HostListener, Inject } from '@angular/core' import { NavigationEnd, NavigationStart, Router } from '@angular/router' -import { tap } from 'rxjs/operators' +import { catchError, tap } from 'rxjs/operators' import { PlatformInfo } from './cdk/platform-info' import { PlatformInfoService } from './cdk/platform-info/platform-info.service' import { WINDOW } from './cdk/window' import { HeadlessOnOauthRoutes } from './constants' import { UserService } from './core' -import { GoogleAnalyticsService } from './core/google-analytics/google-analytics.service' +import { GoogleUniversalAnalyticsService } from './core/google-analytics/google-universal-analytics.service' import { ZendeskService } from './core/zendesk/zendesk.service' +import { GoogleTagManagerService } from './core/google-tag-manager/google-tag-manager.service' +import { + finishPerformanceMeasurement, + reportNavigationStart, +} from './analytics-utils' +import { ERROR_REPORT } from './errors' +import { ErrorHandlerService } from './core/error-handler/error-handler.service' @Component({ selector: 'app-root', @@ -35,9 +42,11 @@ export class AppComponent { constructor( _platformInfo: PlatformInfoService, _router: Router, - _googleAnalytics: GoogleAnalyticsService, + _googleAnalytics: GoogleUniversalAnalyticsService, + _googleTagManagerService: GoogleTagManagerService, _zendesk: ZendeskService, private _userService: UserService, + private _errorHandler: ErrorHandlerService, @Inject(WINDOW) private _window: Window ) { _platformInfo @@ -71,12 +80,37 @@ export class AppComponent { _router.events.subscribe((event) => { if (event instanceof NavigationStart) { - _googleAnalytics.reportNavigationStart(event.url) + reportNavigationStart(event.url) this.currentRoute = event.url } if (event instanceof NavigationEnd) { - _googleAnalytics.reportNavigationEnd(event.url) - _googleAnalytics.reportPageView(event.urlAfterRedirects) + const duration = finishPerformanceMeasurement(event.url) + _googleTagManagerService + .addGtmToDom() + .pipe( + catchError((err) => + this._errorHandler.handleError( + err, + ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA + ) + ) + ) + .subscribe((response) => { + if (response) { + _googleAnalytics + .reportNavigationEnd(event.url, duration) + .subscribe(() => { + _googleAnalytics.reportPageView(event.urlAfterRedirects) + }) + _googleTagManagerService + .reportNavigationEnd(event.url, duration) + .subscribe(() => { + _googleTagManagerService.reportPageView( + event.urlAfterRedirects + ) + }) + } + }) } }) } diff --git a/src/app/authorize/components/form-authorize/form-authorize.component.spec.ts b/src/app/authorize/components/form-authorize/form-authorize.component.spec.ts index 4898d08771..73af278162 100644 --- a/src/app/authorize/components/form-authorize/form-authorize.component.spec.ts +++ b/src/app/authorize/components/form-authorize/form-authorize.component.spec.ts @@ -17,25 +17,23 @@ describe('FormAuthorizeComponent', () => { let component: FormAuthorizeComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule], - declarations: [FormAuthorizeComponent], - providers: [ - WINDOW_PROVIDERS, - UserService, - OauthService, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [FormAuthorizeComponent], + providers: [ + WINDOW_PROVIDERS, + UserService, + OauthService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(FormAuthorizeComponent) diff --git a/src/app/authorize/components/form-authorize/form-authorize.component.ts b/src/app/authorize/components/form-authorize/form-authorize.component.ts index c1cddb2d79..ffbf606792 100644 --- a/src/app/authorize/components/form-authorize/form-authorize.component.ts +++ b/src/app/authorize/components/form-authorize/form-authorize.component.ts @@ -7,7 +7,7 @@ import { WINDOW } from 'src/app/cdk/window' import { ApplicationRoutes } from 'src/app/constants' import { UserService } from 'src/app/core' import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' -import { GoogleAnalyticsService } from 'src/app/core/google-analytics/google-analytics.service' +import { GoogleUniversalAnalyticsService } from 'src/app/core/google-analytics/google-universal-analytics.service' import { OauthService } from 'src/app/core/oauth/oauth.service' import { SignInService } from 'src/app/core/sign-in/sign-in.service' import { TrustedIndividualsService } from 'src/app/core/trusted-individuals/trusted-individuals.service' @@ -19,6 +19,7 @@ import { TrustedIndividuals, } from 'src/app/types/trusted-individuals.endpoint' import { environment } from 'src/environments/environment' +import { GoogleTagManagerService } from '../../../core/google-tag-manager/google-tag-manager.service' @Component({ selector: 'app-form-authorize', @@ -41,7 +42,8 @@ export class FormAuthorizeComponent implements OnInit, OnDestroy { @Inject(WINDOW) private window: Window, private _user: UserService, private _oauth: OauthService, - private _gtag: GoogleAnalyticsService, + private _gtag: GoogleUniversalAnalyticsService, + private _googleTagManagerService: GoogleTagManagerService, private _signingService: SignInService, private _platformInfo: PlatformInfoService, private _router: Router, @@ -92,36 +94,43 @@ export class FormAuthorizeComponent implements OnInit, OnDestroy { if (value) { analyticsReports.push( - this._gtag - .reportEvent(`Authorize`, 'RegGrowth', this.oauthRequest) - .pipe( - catchError((err) => - this._errorHandler.handleError( - err, - ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA - ) - ) - ) + this._gtag.reportEvent(`Authorize`, 'RegGrowth', this.oauthRequest) + ) + analyticsReports.push( + this._googleTagManagerService.reportEvent( + `Authorize`, + this.oauthRequest + ) ) } else { // Create a GA for deny access analyticsReports.push( - this._gtag - .reportEvent(`Authorize_Deny`, 'Disengagement', this.oauthRequest) - .pipe( - catchError((err) => - this._errorHandler.handleError( - err, - ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA - ) - ) - ) + this._gtag.reportEvent( + `Authorize_Deny`, + 'Disengagement', + this.oauthRequest + ) + ) + analyticsReports.push( + this._googleTagManagerService.reportEvent( + `Authorize_Deny`, + this.oauthRequest + ) ) } - forkJoin(analyticsReports).subscribe( - () => (this.window as any).outOfRouterNavigation(data.redirectUrl), - () => (this.window as any).outOfRouterNavigation(data.redirectUrl) - ) + forkJoin(analyticsReports) + .pipe( + catchError((err) => + this._errorHandler.handleError( + err, + ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA + ) + ) + ) + .subscribe( + () => (this.window as any).outOfRouterNavigation(data.redirectUrl), + () => (this.window as any).outOfRouterNavigation(data.redirectUrl) + ) }) } diff --git a/src/app/cdk/account-panel/settings-panels/settings-panels.component.spec.ts b/src/app/cdk/account-panel/settings-panels/settings-panels.component.spec.ts index 810dfaafdd..664857f40f 100644 --- a/src/app/cdk/account-panel/settings-panels/settings-panels.component.spec.ts +++ b/src/app/cdk/account-panel/settings-panels/settings-panels.component.spec.ts @@ -14,27 +14,21 @@ describe('SettingsPanelsComponent', () => { let component: SettingsPanelsComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - MatDialogModule, - RouterTestingModule, - ], - declarations: [SettingsPanelsComponent], - providers: [ - WINDOW_PROVIDERS, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MatDialogModule, RouterTestingModule], + declarations: [SettingsPanelsComponent], + providers: [ + WINDOW_PROVIDERS, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(SettingsPanelsComponent) diff --git a/src/app/cdk/modal/modal-header/modal-header.component.spec.ts b/src/app/cdk/modal/modal-header/modal-header.component.spec.ts index 47081b5d88..a59c77d1df 100644 --- a/src/app/cdk/modal/modal-header/modal-header.component.spec.ts +++ b/src/app/cdk/modal/modal-header/modal-header.component.spec.ts @@ -19,25 +19,23 @@ describe('ModalHeaderComponent', () => { let component: ModalHeaderComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule], - declarations: [ModalHeaderComponent], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - WINDOW_PROVIDERS, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [ModalHeaderComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + WINDOW_PROVIDERS, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(ModalHeaderComponent) diff --git a/src/app/cdk/modal/modal-side-bar/modal-side-bar.component.html b/src/app/cdk/modal/modal-side-bar/modal-side-bar.component.html index f975b29a3c..d106411c50 100644 --- a/src/app/cdk/modal/modal-side-bar/modal-side-bar.component.html +++ b/src/app/cdk/modal/modal-side-bar/modal-side-bar.component.html @@ -25,7 +25,16 @@
  • - + + + + +
  • +
  • + + + +
  • diff --git a/src/app/cdk/modal/modal-side-bar/modal-side-bar.component.spec.ts b/src/app/cdk/modal/modal-side-bar/modal-side-bar.component.spec.ts index fc7a79b5ad..583927f32a 100644 --- a/src/app/cdk/modal/modal-side-bar/modal-side-bar.component.spec.ts +++ b/src/app/cdk/modal/modal-side-bar/modal-side-bar.component.spec.ts @@ -6,13 +6,11 @@ describe('ModalSideBarComponent', () => { let component: ModalSideBarComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ModalSideBarComponent], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ModalSideBarComponent], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(ModalSideBarComponent) diff --git a/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.html b/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.html index a65cc776e0..2bc28c15f9 100644 --- a/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.html +++ b/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.html @@ -1,16 +1,21 @@ -

    - - Your email couldn't be verified, please check the link you used to - verify it. - - - Thank you for verifying your email - -

    +
    +

    + + Your email couldn't be verified, please check the link you used to + verify it. + + + Thank you for verifying your email + +

    +
    diff --git a/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.scss b/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.scss index e10528f5be..3538c0f31d 100644 --- a/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.scss +++ b/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.scss @@ -6,9 +6,11 @@ mat-toolbar { mat-toolbar-row { text-align: center; - p { + div { width: 100%; - white-space: pre-wrap; + p { + white-space: pre-wrap; + } } } } diff --git a/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.spec.ts b/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.spec.ts index 02cc56e294..e1dc8f66ce 100644 --- a/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.spec.ts +++ b/src/app/cdk/my-orcid-alerts/my-orcid-alerts.component.spec.ts @@ -6,13 +6,11 @@ describe('MyOrcidAlertsComponent', () => { let component: MyOrcidAlertsComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [MyOrcidAlertsComponent], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [MyOrcidAlertsComponent], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(MyOrcidAlertsComponent) diff --git a/src/app/cdk/panel/panel-data-line/panel-data-line.component.spec.ts b/src/app/cdk/panel/panel-data-line/panel-data-line.component.spec.ts index aaebc9a930..6d0d609a0f 100644 --- a/src/app/cdk/panel/panel-data-line/panel-data-line.component.spec.ts +++ b/src/app/cdk/panel/panel-data-line/panel-data-line.component.spec.ts @@ -6,13 +6,11 @@ describe('PanelDataLineComponent', () => { let component: PanelDataLineComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [PanelDataLineComponent], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [PanelDataLineComponent], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(PanelDataLineComponent) diff --git a/src/app/cdk/panel/panel-data/panel-data.component.spec.ts b/src/app/cdk/panel/panel-data/panel-data.component.spec.ts index d7de166372..805553e45c 100644 --- a/src/app/cdk/panel/panel-data/panel-data.component.spec.ts +++ b/src/app/cdk/panel/panel-data/panel-data.component.spec.ts @@ -15,23 +15,21 @@ describe('PanelDataComponent', () => { let component: PanelDataComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule], - declarations: [PanelDataComponent], - providers: [ - WINDOW_PROVIDERS, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [PanelDataComponent], + providers: [ + WINDOW_PROVIDERS, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(PanelDataComponent) diff --git a/src/app/cdk/panel/panel-expand-buttons/panel-expand-buttons.component.html b/src/app/cdk/panel/panel-expand-buttons/panel-expand-buttons.component.html index c779e33726..db8adb011e 100644 --- a/src/app/cdk/panel/panel-expand-buttons/panel-expand-buttons.component.html +++ b/src/app/cdk/panel/panel-expand-buttons/panel-expand-buttons.component.html @@ -3,7 +3,9 @@ (click)="toggle.next()" [matTooltip]="tooltipLabelShowDetails" *ngIf="!openState" - [attr.aria-label]="panelType | appPanelsExpandAriaLabel: panelId:panelTitle" + [attr.aria-label]=" + panelType | appPanelsExpandAriaLabel : panelId : panelTitle + " class="expand_more" > expand_less @@ -13,7 +15,9 @@ (click)="toggle.next()" [matTooltip]="tooltipLabelHideDetails" *ngIf="openState" - [attr.aria-label]="panelType | appPanelsCollapseAriaLabel: panelId:panelTitle" + [attr.aria-label]=" + panelType | appPanelsCollapseAriaLabel : panelId : panelTitle + " class="expand_less" > expand_more diff --git a/src/app/cdk/panel/panel-source/panel-source.component.html b/src/app/cdk/panel/panel-source/panel-source.component.html index 0f3a1d3c37..8c45db7ef3 100644 --- a/src/app/cdk/panel/panel-source/panel-source.component.html +++ b/src/app/cdk/panel/panel-source/panel-source.component.html @@ -68,7 +68,7 @@ mat-icon-button [matTooltip]="labelDeleteButton" [attr.aria-label]=" - type | appPanelActivityActionAriaLabel: 'delete':this.panelTitle + type | appPanelActivityActionAriaLabel : 'delete' : this.panelTitle " (click)="delete()" *ngIf="(stackLength > 1 && displayTheStack) || stackLength === 1" diff --git a/src/app/cdk/panel/panel-source/panel-source.component.spec.ts b/src/app/cdk/panel/panel-source/panel-source.component.spec.ts index f7caecae5f..c1e99b7ec7 100644 --- a/src/app/cdk/panel/panel-source/panel-source.component.spec.ts +++ b/src/app/cdk/panel/panel-source/panel-source.component.spec.ts @@ -11,33 +11,29 @@ import { MatSnackBar } from '@angular/material/snack-bar' import { MatDialog, MatDialogModule } from '@angular/material/dialog' import { Overlay } from '@angular/cdk/overlay' import { VerificationEmailModalService } from '../../../core/verification-email-modal/verification-email-modal.service' +import { AppPanelActivityActionAriaLabelPipe } from '../../../shared/pipes/app-panel-activity-action-aria-label/app-panel-activity-action-aria-label.pipe' describe('PanelSourceComponent', () => { let component: PanelSourceComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - MatDialogModule, - RouterTestingModule, - ], - declarations: [PanelSourceComponent], - providers: [ - WINDOW_PROVIDERS, - VerificationEmailModalService, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MatDialogModule, RouterTestingModule], + declarations: [PanelSourceComponent], + providers: [ + AppPanelActivityActionAriaLabelPipe, + WINDOW_PROVIDERS, + VerificationEmailModalService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(PanelSourceComponent) diff --git a/src/app/cdk/panel/panel-source/panel-source.component.ts b/src/app/cdk/panel/panel-source/panel-source.component.ts index 9a6db4d917..d57f1c10cf 100644 --- a/src/app/cdk/panel/panel-source/panel-source.component.ts +++ b/src/app/cdk/panel/panel-source/panel-source.component.ts @@ -1,7 +1,10 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { PlatformInfoService } from '../../platform-info' import { first } from 'rxjs/operators' -import { Affiliation } from '../../../types/record-affiliation.endpoint' +import { + Affiliation, + AffiliationTypeLabel, +} from '../../../types/record-affiliation.endpoint' import { MatDialog } from '@angular/material/dialog' import { ModalDeleteItemsComponent } from '../../../record/components/modals/modal-delete-item/modal-delete-items.component' import { Funding } from '../../../types/record-funding.endpoint' @@ -9,6 +12,7 @@ import { ResearchResource } from '../../../types/record-research-resources.endpo import { Work } from '../../../types/record-works.endpoint' import { PeerReview } from '../../../types/record-peer-review.endpoint' import { VerificationEmailModalService } from 'src/app/core/verification-email-modal/verification-email-modal.service' +import { AppPanelActivityActionAriaLabelPipe } from '../../../shared/pipes/app-panel-activity-action-aria-label/app-panel-activity-action-aria-label.pipe' @Component({ selector: 'app-panel-source', @@ -17,8 +21,8 @@ import { VerificationEmailModalService } from 'src/app/core/verification-email-m preserveWhitespaces: true, }) export class PanelSourceComponent implements OnInit { - closeOtherSources = $localize`:@@record.closeOtherSources:Close other sources` - openOtherSources = $localize`:@@record.openOtherSources:Open other sources` + closeOtherSources = $localize`:@@record.hideAllSources:Hide all sources for` + openOtherSources = $localize`:@@record.showAllSources:Show all sources for` @Input() isPublicRecord @Input() isPreferred = true @@ -65,7 +69,8 @@ export class PanelSourceComponent implements OnInit { constructor( private _verificationEmailModalService: VerificationEmailModalService, private _dialog: MatDialog, - private _platformInfo: PlatformInfoService + private _platformInfo: PlatformInfoService, + private _activityAction: AppPanelActivityActionAriaLabelPipe ) { this._platformInfo.get().subscribe((person) => { this.isHandset = person.handset @@ -75,6 +80,10 @@ export class PanelSourceComponent implements OnInit { if (!this.panelTitle) { this.panelTitle = '' } + const itemType = this._activityAction.transform(this.type, null, null) + const typeAndTitle = `${itemType} ${this.panelTitle}` + this.closeOtherSources += typeAndTitle + this.openOtherSources += typeAndTitle } toggleStackMode() { diff --git a/src/app/cdk/panel/panel.module.ts b/src/app/cdk/panel/panel.module.ts index 89edd6504b..b1ab3ab46d 100644 --- a/src/app/cdk/panel/panel.module.ts +++ b/src/app/cdk/panel/panel.module.ts @@ -23,6 +23,7 @@ import { PrivacySelectorModule } from '../privacy-selector/privacy-selector.modu import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { MatProgressBarModule } from '@angular/material/progress-bar' import { SharedModule } from '../../shared/shared.module' +import { AppPanelActivityActionAriaLabelPipe } from '../../shared/pipes/app-panel-activity-action-aria-label/app-panel-activity-action-aria-label.pipe' @NgModule({ declarations: [ @@ -65,5 +66,6 @@ import { SharedModule } from '../../shared/shared.module' PanelElementSourceComponent, PanelExpandButtonsComponent, ], + providers: [AppPanelActivityActionAriaLabelPipe], }) export class PanelModule {} diff --git a/src/app/cdk/panel/panel/panel.component.html b/src/app/cdk/panel/panel/panel.component.html index 8795d407e1..390a61e7ca 100644 --- a/src/app/cdk/panel/panel/panel.component.html +++ b/src/app/cdk/panel/panel/panel.component.html @@ -29,7 +29,7 @@
    diff --git a/src/app/cdk/panel/panels/panels.component.html b/src/app/cdk/panel/panels/panels.component.html index 0a93842722..bd254ce379 100644 --- a/src/app/cdk/panel/panels/panels.component.html +++ b/src/app/cdk/panel/panels/panels.component.html @@ -155,7 +155,7 @@

    mat-button class="orc-font-body-small" [attr.aria-label]=" - type | appPanelsSortAriaLabel: !currentAmount && !total + type | appPanelsSortAriaLabel : !currentAmount && !total " [matMenuTriggerFor]="menu" [disabled]="!currentAmount && !total" @@ -171,16 +171,20 @@

    > {{ option | sortLabel }} - south + north - north + south diff --git a/src/app/cdk/panel/panels/panels.component.spec.ts b/src/app/cdk/panel/panels/panels.component.spec.ts index 5a599eb173..aad08caf1f 100644 --- a/src/app/cdk/panel/panels/panels.component.spec.ts +++ b/src/app/cdk/panel/panels/panels.component.spec.ts @@ -27,34 +27,32 @@ describe('PanelsComponent', () => { let fixture: ComponentFixture let loader: HarnessLoader - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - MatDialogModule, - MatMenuModule, - NoopAnimationsModule, - PanelModule, - RecordModule, - RouterTestingModule, - SharedModule, - ], - declarations: [PanelsComponent], - providers: [ - UntypedFormBuilder, - WINDOW_PROVIDERS, - RegisterService, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + MatDialogModule, + MatMenuModule, + NoopAnimationsModule, + PanelModule, + RecordModule, + RouterTestingModule, + SharedModule, + ], + declarations: [PanelsComponent], + providers: [ + UntypedFormBuilder, + WINDOW_PROVIDERS, + RegisterService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(PanelsComponent) diff --git a/src/app/cdk/panel/panels/panels.component.ts b/src/app/cdk/panel/panels/panels.component.ts index ceab08f226..fd775e9769 100644 --- a/src/app/cdk/panel/panels/panels.component.ts +++ b/src/app/cdk/panel/panels/panels.component.ts @@ -69,6 +69,9 @@ export class PanelsComponent implements OnInit { @Input() labelSortButton = $localize`:@@shared.addItem:Add Item` IS_QA: boolean + ariaLabelAscending = $localize`:@@shared.ariaLabelAscending:Ascending` + ariaLabelDescending = $localize`:@@shared.ariaLabelDescending:Descending` + constructor( private _dialog: MatDialog, private _platform: PlatformInfoService, diff --git a/src/app/cdk/platform-info/browserlist.regexp.ts b/src/app/cdk/platform-info/browserlist.regexp.ts index 954be17405..dd011b22dc 100644 --- a/src/app/cdk/platform-info/browserlist.regexp.ts +++ b/src/app/cdk/platform-info/browserlist.regexp.ts @@ -1,2 +1,3 @@ // tslint:disable-next-line: max-line-length -export const BROWSERLIST_REGEXP = /((CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS)[ +]+(13[_.]4|13[_.]([5-9]|\d{2,})|13[_.]7|13[_.]([8-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})[_.]\d+|14[_.]0|14[_.]([1-9]|\d{2,})|14[_.]4|14[_.]([5-9]|\d{2,})|14[_.]8|14[_.](9|\d{2,})|(1[5-9]|[2-9]\d|\d{3,})[_.]\d+|15[_.]0|15[_.]([1-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})[_.]\d+|16[_.]0|16[_.]([1-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})[_.]\d+)(?:[_.]\d+)?)|((?:Chrome).*OPR\/(74|(7[5-9]|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Edge\/(80|(8[1-9]|9\d|\d{3,})|83|(8[4-9]|9\d|\d{3,}))(?:\.\d+)?)|((Chromium|Chrome)\/(80|(8[1-9]|9\d|\d{3,})|83|(8[4-9]|9\d|\d{3,}))\.\d+(?:\.\d+)?)|(Version\/(13\.1|13\.([2-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})\.\d+|14\.0|14\.([1-9]|\d{2,})|(1[5-9]|[2-9]\d|\d{3,})\.\d+|15\.0|15\.([1-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})\.\d+|16\.0|16\.([1-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})\.\d+)(?:\.\d+)? Safari\/)|(Firefox\/(78|(79|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Firefox\/(78|(79|[8-9]\d|\d{3,}))\.\d+(pre|[ab]\d+[a-z]*)?)/ +export const BROWSERLIST_REGEXP = + /((CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS)[ +]+(13[_.]4|13[_.]([5-9]|\d{2,})|13[_.]7|13[_.]([8-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})[_.]\d+|14[_.]0|14[_.]([1-9]|\d{2,})|14[_.]4|14[_.]([5-9]|\d{2,})|14[_.]8|14[_.](9|\d{2,})|(1[5-9]|[2-9]\d|\d{3,})[_.]\d+|15[_.]0|15[_.]([1-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})[_.]\d+|16[_.]0|16[_.]([1-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})[_.]\d+)(?:[_.]\d+)?)|((?:Chrome).*OPR\/(74|(7[5-9]|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Edge\/(80|(8[1-9]|9\d|\d{3,})|83|(8[4-9]|9\d|\d{3,}))(?:\.\d+)?)|((Chromium|Chrome)\/(80|(8[1-9]|9\d|\d{3,})|83|(8[4-9]|9\d|\d{3,}))\.\d+(?:\.\d+)?)|(Version\/(13\.1|13\.([2-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})\.\d+|14\.0|14\.([1-9]|\d{2,})|(1[5-9]|[2-9]\d|\d{3,})\.\d+|15\.0|15\.([1-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})\.\d+|16\.0|16\.([1-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})\.\d+)(?:\.\d+)? Safari\/)|(Firefox\/(78|(79|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Firefox\/(78|(79|[8-9]\d|\d{3,}))\.\d+(pre|[ab]\d+[a-z]*)?)/ diff --git a/src/app/cdk/platform-info/platform-info.service.ts b/src/app/cdk/platform-info/platform-info.service.ts index 822eb7fbbf..8605f24ca5 100644 --- a/src/app/cdk/platform-info/platform-info.service.ts +++ b/src/app/cdk/platform-info/platform-info.service.ts @@ -76,9 +76,8 @@ export class PlatformInfoService { this.platform.queryParameters = queryParameters const previousOauthState = this.hasOauthParameters() const previousSocialState = this.updateSocialState(queryParameters) - const previousInstitutionalState = this.updatesInstitutionalState( - queryParameters - ) + const previousInstitutionalState = + this.updatesInstitutionalState(queryParameters) if ( this.platform.hasOauthParameters !== previousOauthState || this.platform.social !== previousSocialState || diff --git a/src/app/cdk/side-bar/modals/modal-country/modal-country.component.html b/src/app/cdk/side-bar/modals/modal-country/modal-country.component.html index 53a1dda19d..760b00d208 100644 --- a/src/app/cdk/side-bar/modals/modal-country/modal-country.component.html +++ b/src/app/cdk/side-bar/modals/modal-country/modal-country.component.html @@ -77,8 +77,10 @@ src="./assets/vectors/draggable.svg" aria-label="drag handle" /> + @@ -112,6 +114,33 @@ > + + + + + + +

    { let component: ModalCountryComponent @@ -110,7 +111,7 @@ describe('ModalCountryComponent', () => { ).toBe('Add another country or location') }) - it('should display 4 countries with the next status disabled (source is not user), enabled and enabled', async () => { + it('should display 4 countries, 2 read only (source is not the user) and 2 editable ', async () => { const adressesResponse: Observable = of({ errors: [], addresses: getAddresses(), @@ -131,13 +132,14 @@ describe('ModalCountryComponent', () => { fixture.detectChanges() const countriesSelects = await loader.getAllHarnesses(MatSelectHarness) + const countriesInputs = await loader.getAllHarnesses(MatInputHarness) - await countriesSelects[3].open() - await countriesSelects[3].clickOptions() //Select the first option + await countriesSelects[1].open() + await countriesSelects[1].clickOptions() //Select the first option - const disabledStates = await parallel(() => - countriesSelects.map((select) => select.isDisabled()) - ) + // const disabledStates = await parallel(() => + // countriesSelects.map((select) => select.isDisabled()) + // ) expect(countriesForm.controls[1].value.country).toBe('Albania') expect(countriesForm.controls[2].getRawValue().country).toBe( @@ -145,8 +147,8 @@ describe('ModalCountryComponent', () => { ) expect(countriesForm.controls[3].getRawValue().country).toBe('Kosovo') expect(countriesForm.controls['new-0'].value.country).toBe('Afghanistan') - expect(disabledStates.filter((value) => value).length).toBe(2) - expect(countriesSelects.length).toBe(4) + expect(countriesInputs.length).toBe(2) + expect(countriesSelects.length).toBe(2) }) }) diff --git a/src/app/cdk/side-bar/modals/modal-country/modal-country.component.ts b/src/app/cdk/side-bar/modals/modal-country/modal-country.component.ts index 53849e0329..2eb621d27e 100644 --- a/src/app/cdk/side-bar/modals/modal-country/modal-country.component.ts +++ b/src/app/cdk/side-bar/modals/modal-country/modal-country.component.ts @@ -35,6 +35,7 @@ export class ModalCountryComponent implements OnInit, OnDestroy { ariaLabelCancel = $localize`:@@side-bar.ariaLabelCountryCancel:Cancel changes and close Countries` ariaLabelDelete = $localize`:@@side-bar.ariaLabelCountryDelete:Delete Country or location` ariaLabelSelect = $localize`:@@side-bar.ariaLabelCountrySelect:Select country or location` + ariaLabelCountryLocationReadOnly = $localize`:@@side-bar.ariaLabelCountrySelect:Country or location` ariaLabelClose = $localize`:@@side-bar.ariaLabelCountryClose:Close Countries` $destroy: Subject = new Subject() @@ -61,6 +62,7 @@ export class ModalCountryComponent implements OnInit, OnDestroy { @ViewChildren('countrySelect') inputs: QueryList ngOrcidCountry = $localize`:@@shared.selectACountryOrLocation:Select a country or location` + countryOrLocationLabel = $localize`:@@shared.countryOrLocation:Country or location` ngOnInit(): void { this._recordCountryService @@ -99,10 +101,7 @@ export class ModalCountryComponent implements OnInit, OnDestroy { countries.forEach((country) => { group[country.putCode] = new UntypedFormGroup({ - country: new UntypedFormControl({ - value: country.countryName, - disabled: country.source !== this.id, - }), + country: new UntypedFormControl(country.countryName), visibility: new UntypedFormControl(country.visibility.visibility, {}), }) }) diff --git a/src/app/cdk/side-bar/modals/modal-email/modal-email.component.html b/src/app/cdk/side-bar/modals/modal-email/modal-email.component.html index 4be3ade988..adfd363626 100644 --- a/src/app/cdk/side-bar/modals/modal-email/modal-email.component.html +++ b/src/app/cdk/side-bar/modals/modal-email/modal-email.component.html @@ -39,7 +39,7 @@ rel="noopener noreferrer" href="/content/orcid-terms-use" i18n="@@side-bar.termOfUse" - [attr.ariaLabel]="ariaLabelOrcidTermsOfUseLink" + [attr.aria-label]="ariaLabelOrcidTermsOfUseLink" >ORCID terms of use diff --git a/src/app/cdk/side-bar/modals/modal-email/modal-email.component.ts b/src/app/cdk/side-bar/modals/modal-email/modal-email.component.ts index 0e0baa27b2..17ae03544d 100644 --- a/src/app/cdk/side-bar/modals/modal-email/modal-email.component.ts +++ b/src/app/cdk/side-bar/modals/modal-email/modal-email.component.ts @@ -42,11 +42,11 @@ import { OrcidValidators } from 'src/app/validators' preserveWhitespaces: true, }) export class ModalEmailComponent implements OnInit, OnDestroy { - ariaLabelKnowledgeBase = $localize`:@@side-bar.ariaLabelOrcidTermsOfUseBase:ORCID terms of use page (Opens in a new tab)` + ariaLabelKnowledgeBase = $localize`:@@side-bar.ariaLabelOrcidTermsOfUseBase:ORCID knowledge base (Opens in a new tab)` ariaLabelKnowledgeSupport = $localize`:@@side-bar.ariaLabelOrcidTermsSupport:ORCID support page (Opens in a new tab)` - ariaLabelOrcidTermsOfUseLink = $localize`:@@side-bar.ariaLabelOrcidTermsOfUseLink:ORCID terms of use` + ariaLabelOrcidTermsOfUseLink = $localize`:@@side-bar.ariaLabelOrcidTermsOfUseLink:ORCID terms of use (Opens in a new tab)` ariaLabelSave = $localize`:@@side-bar.ariaLabelEmailSave:Save changes to Emails` - ariaLabelCancel = $localize`:@@side-bar.ariaLabelEmailCancel:Cancel changes to Emails` + ariaLabelCancel = $localize`:@@side-bar.ariaLabelEmailCancel:Cancel changes and close Emails` ariaLabelDelete = $localize`:@@side-bar.ariaLabelEmailDelete:Delete Email` ariaLabelClose = $localize`:@@side-bar.ariaLabelEmailClose:Close Emails` ariaLabelEmail = $localize`:@@side-bar.ariaLabelEmail:Emails` @@ -226,9 +226,9 @@ export class ModalEmailComponent implements OnInit, OnDestroy { private triggerGeneralFormValidation() { Object.keys(this.emailsForm.controls).forEach((currentControlKey) => { - ;(this.emailsForm.controls[ - currentControlKey - ] as UntypedFormGroup).controls.email.updateValueAndValidity() + ;( + this.emailsForm.controls[currentControlKey] as UntypedFormGroup + ).controls.email.updateValueAndValidity() }) } @@ -243,9 +243,8 @@ export class ModalEmailComponent implements OnInit, OnDestroy { allEmailsAreUnique(controlKey): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const formGroup = this.emailsForm - const formGroupKeysWithDuplicatedValues: string[] = this.listDuplicateInputKeys( - formGroup - ) + const formGroupKeysWithDuplicatedValues: string[] = + this.listDuplicateInputKeys(formGroup) this.removeDuplicateErrorFromOtherControls( formGroupKeysWithDuplicatedValues, this.emailsForm @@ -282,9 +281,9 @@ export class ModalEmailComponent implements OnInit, OnDestroy { emailsForm: UntypedFormGroup = new UntypedFormGroup({}) ): void { Object.keys(emailsForm.controls).forEach((currentControlKey) => { - const otherEmailControl = (emailsForm.controls[ - currentControlKey - ] as UntypedFormGroup).controls.email as UntypedFormControl + const otherEmailControl = ( + emailsForm.controls[currentControlKey] as UntypedFormGroup + ).controls.email as UntypedFormControl if ( formGroupKeysWithDuplicatedValues.indexOf(currentControlKey) === -1 && otherEmailControl.errors && @@ -315,9 +314,9 @@ export class ModalEmailComponent implements OnInit, OnDestroy { emailControlX = emailControlX.toLowerCase().trim() Object.keys(formGroup.controls).forEach((keyY) => { - let emailControlY: string = (formGroup.controls[ - keyY - ] as UntypedFormGroup).controls['email'].value + let emailControlY: string = ( + formGroup.controls[keyY] as UntypedFormGroup + ).controls['email'].value emailControlY = emailControlY.toLowerCase().trim() // Only if both controls are not empty diff --git a/src/app/cdk/side-bar/modals/modal-keyword/modal-keyword.component.html b/src/app/cdk/side-bar/modals/modal-keyword/modal-keyword.component.html index 88564b6420..345258da49 100644 --- a/src/app/cdk/side-bar/modals/modal-keyword/modal-keyword.component.html +++ b/src/app/cdk/side-bar/modals/modal-keyword/modal-keyword.component.html @@ -73,8 +73,12 @@

    { let component: ModalKeywordComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule], - declarations: [ModalKeywordComponent], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - WINDOW_PROVIDERS, - RecordKeywordService, - UserService, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [ModalKeywordComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + WINDOW_PROVIDERS, + RecordKeywordService, + UserService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(ModalKeywordComponent) diff --git a/src/app/cdk/side-bar/modals/modal-keyword/modal-keyword.component.ts b/src/app/cdk/side-bar/modals/modal-keyword/modal-keyword.component.ts index 479a94a225..b9ef06cce4 100644 --- a/src/app/cdk/side-bar/modals/modal-keyword/modal-keyword.component.ts +++ b/src/app/cdk/side-bar/modals/modal-keyword/modal-keyword.component.ts @@ -111,16 +111,10 @@ export class ModalKeywordComponent implements OnInit, OnDestroy { keywords.forEach((keyword) => { group[keyword.putCode] = new UntypedFormGroup({ - content: new UntypedFormControl( - { - value: keyword.content, - disabled: keyword.source !== this.id, - }, - { - validators: [Validators.maxLength(this.keywordMaxLength)], - updateOn: 'change', - } - ), + content: new UntypedFormControl(keyword.content, { + validators: [Validators.maxLength(this.keywordMaxLength)], + updateOn: 'change', + }), visibility: new UntypedFormControl(keyword.visibility.visibility, {}), }) }) diff --git a/src/app/cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component.html b/src/app/cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component.html index aef0e131d9..0c517436ed 100644 --- a/src/app/cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component.html +++ b/src/app/cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component.html @@ -1,5 +1,7 @@ - Other IDs @@ -93,7 +95,13 @@ />
    { let component: ModalPersonIdentifiersComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule], - declarations: [ModalPersonIdentifiersComponent], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - WINDOW_PROVIDERS, - RecordPersonIdentifierService, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [ModalPersonIdentifiersComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + WINDOW_PROVIDERS, + RecordPersonIdentifierService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(ModalPersonIdentifiersComponent) diff --git a/src/app/cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component.ts b/src/app/cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component.ts index e355daba2e..2be1760054 100644 --- a/src/app/cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component.ts +++ b/src/app/cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component.ts @@ -18,12 +18,13 @@ import { PersonIdentifierEndpoint } from 'src/app/types/record-person-identifier preserveWhitespaces: true, }) export class ModalPersonIdentifiersComponent implements OnInit, OnDestroy { - ariaLabelSave = $localize`:@@side-bar.ariaLabelPersonalIdSave:Save changes to Other IDs` - ariaLabelCancel = $localize`:@@side-bar.ariaLabelPersonalIdCancel:Cancel changes to Other IDs and close Other identifiers` + ariaLabelSave = $localize`:@@side-bar.ariaLabelPersonalIdSave:Save changes to Other identifiers` + ariaLabelCancel = $localize`:@@side-bar.ariaLabelPersonalIdCancel:Cancel changes and close Other identifiers` + ariaLabelClose = $localize`:@@side-bar.ariaLabelPersonalIdClose:Close Other identifiers` ariaLabelDelete = $localize`:@@share.ariaLabelDelete:Delete identifier` ariaLabelIdentifier = $localize`:@@share.ariaLabelIdentifier:Identifier` ariaLabelOtherIdsSupport = $localize`:@@side-bar.ariaLabelOtherIdsSupport:Find out how to add other identifiers to your ORCID record (Opens in a new tab)` - ariaLabelIdentifierUrl = $localize`:@@side-bar.ariaLabelUrl:Open identifier page (Opens in a new tab)` + ariaLabelIdentifierUrl = $localize`:@@side-bar.ariaLabelUrl:(Opens in a new tab)` $destroy: Subject = new Subject() constructor( diff --git a/src/app/cdk/side-bar/modals/modal-websites/modal-websites.component.html b/src/app/cdk/side-bar/modals/modal-websites/modal-websites.component.html index d104424f28..2417da1baa 100644 --- a/src/app/cdk/side-bar/modals/modal-websites/modal-websites.component.html +++ b/src/app/cdk/side-bar/modals/modal-websites/modal-websites.component.html @@ -75,141 +75,214 @@

    />

    -
    - {{ website.urlName }} -
    - - - - - Must be less than 355 characters - - -
    - {{ website.url.value }} + + + Must be less than 355 characters + + - - URL can not be duplicated - - - + + An URL is required + + + + Must be less than 2000 characters + + + - -
    - + Invalid URL + + + URL can not be duplicated + + + + + + + - - - An URL is required - + + + Must be less than 355 characters + + - - Must be less than 2000 characters - + + + An URL is required + - - Invalid URL - - - URL can not be duplicated - - - + Must be less than 2000 characters + + + + Invalid URL + + - - + i18n="@@topBar.urlDuplicated" + > + URL can not be duplicated + + + + + +

    { let component: ModalWebsitesComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule], - declarations: [ModalWebsitesComponent], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - WINDOW_PROVIDERS, - UserService, - RecordWebsitesService, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [ModalWebsitesComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + WINDOW_PROVIDERS, + UserService, + RecordWebsitesService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(ModalWebsitesComponent) diff --git a/src/app/cdk/side-bar/modals/modal-websites/modal-websites.component.ts b/src/app/cdk/side-bar/modals/modal-websites/modal-websites.component.ts index 6a7c35c6a2..6394df6389 100644 --- a/src/app/cdk/side-bar/modals/modal-websites/modal-websites.component.ts +++ b/src/app/cdk/side-bar/modals/modal-websites/modal-websites.component.ts @@ -45,7 +45,7 @@ export class ModalWebsitesComponent implements OnInit, OnDestroy { ariaLabelSave = $localize`:@@side-bar.ariaLabelWebsiteSave:Save changes to Websites & social links` ariaLabelCancel = $localize`:@@side-bar.ariaLabelWebsiteCancel:Cancel changes to Websites & social links` ariaLabelDelete = $localize`:@@side-bar.ariaLabelWebsiteDelete:Delete Websites or social link` - ariaLabelClose = $localize`:@@side-bar.ariaLabelWebsiteClose:Close Websites or social link` + ariaLabelClose = $localize`:@@side-bar.ariaLabelWebsiteClose:Close Websites and social links` ariaLabelTitle = $localize`:@@side-bar.ariaLabelWebsiteTitle:Websites or social link Title` ariaLabelURL = $localize`:@@side-bar.ariaLabelWebsiteUrl:Link URL` @@ -252,9 +252,8 @@ export class ModalWebsitesComponent implements OnInit, OnDestroy { this.websitesForm ) { const formGroup = this.websitesForm - const formGroupKeysWithDuplicatedValues: string[] = this.listDuplicateInputKeys( - formGroup - ) + const formGroupKeysWithDuplicatedValues: string[] = + this.listDuplicateInputKeys(formGroup) this.removeDuplicateErrorFromOtherControls( formGroupKeysWithDuplicatedValues, formGroup @@ -302,9 +301,9 @@ export class ModalWebsitesComponent implements OnInit, OnDestroy { websitesForm: UntypedFormGroup = new UntypedFormGroup({}) ): void { Object.keys(websitesForm.controls).forEach((currentControlKey) => { - const urlControl = (websitesForm.controls[ - currentControlKey - ] as UntypedFormGroup).controls.url as UntypedFormControl + const urlControl = ( + websitesForm.controls[currentControlKey] as UntypedFormGroup + ).controls.url as UntypedFormControl if ( formGroupKeysWithDuplicatedValues.indexOf(currentControlKey) === -1 && urlControl.errors && diff --git a/src/app/cdk/snackbar/snackbar.service.ts b/src/app/cdk/snackbar/snackbar.service.ts index 781bd39c95..c17135141d 100644 --- a/src/app/cdk/snackbar/snackbar.service.ts +++ b/src/app/cdk/snackbar/snackbar.service.ts @@ -4,7 +4,7 @@ import { MatSnackBarHorizontalPosition, } from '@angular/material/snack-bar' import { take } from 'rxjs/operators' -import { GoogleAnalyticsService } from 'src/app/core/google-analytics/google-analytics.service' +import { GoogleUniversalAnalyticsService } from 'src/app/core/google-analytics/google-universal-analytics.service' import { DisplayMessage, ErrorReport, ScreenDirection } from 'src/app/types' import { PlatformInfoService } from '../platform-info' @@ -22,7 +22,7 @@ export class SnackbarService { constructor( private _snackBar: MatSnackBar, _platform: PlatformInfoService, - private _gtag: GoogleAnalyticsService + private _gtag: GoogleUniversalAnalyticsService ) { _platform .get() diff --git a/src/app/constants.ts b/src/app/constants.ts index 2573d3057a..a1eea95f6f 100644 --- a/src/app/constants.ts +++ b/src/app/constants.ts @@ -23,13 +23,15 @@ import { ModalWebsitesComponent } from './cdk/side-bar/modals/modal-websites/mod import { ModalPersonIdentifiersComponent } from './cdk/side-bar/modals/modal-person-identifiers/modal-person-identifiers.component' import { AffiliationType } from './types/record-affiliation.endpoint' import { WorkBibtexModalComponent } from './record/components/work-stack-group/modals/work-bibtex-modal/work-bibtex-modal.component' +import { environment } from 'src/environments/environment' export { COUNTRY_NAMES_TO_COUNTRY_CODES } from './constants-country-codes' // Custom email REGEXP // https://regex101.com/r/jV4aN7/16 // tslint:disable-next-line: max-line-length -export const EMAIL_REGEXP = /^([^@\s]|(".+"))+@([^@\s\."'\(\)\[\]\{\}\\/,:;]+\.)+([^@\s\."'\(\)\[\]\{\}\\/,:;]{2,})+$/ +export const EMAIL_REGEXP = + /^([^@\s]|(".+"))+@([^@\s\."'\(\)\[\]\{\}\\/,:;]+\.)+([^@\s\."'\(\)\[\]\{\}\\/,:;]{2,})+$/ export const EMAIL_REGEXP_GENERIC = /^\s*?(.+)@(.+?)\s*$/ // https://regex101.com/r/9MXmdl/1 @@ -37,13 +39,16 @@ export const ORCID_REGEXP = /(\d{4}[- ]{0,}){3}\d{3}[\dX]$/ export const ORCID_REGEXP_CASE_INSENSITIVE = /(\d{4}[- ]{0,}){3}\d{3}[\dX]$/i // https://regex101.com/r/V95col/6 // tslint:disable-next-line: max-line-length -export const ORCID_URI_REGEXP = /(orcid\.org\/|qa\.orcid\.org\/|sandbox\.orcid\.org\/|dev\.orcid\.org\/|localhost.*)(\d{4}[- ]{0,}){3}\d{3}[\dX]$/i +export const ORCID_URI_REGEXP = + /(orcid\.org\/|qa\.orcid\.org\/|sandbox\.orcid\.org\/|dev\.orcid\.org\/|localhost.*)(\d{4}[- ]{0,}){3}\d{3}[\dX]$/i /* NO PROTOCOL REQUIRED*/ // https://regex101.com/r/M1fqZi/1 -export const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#%[\]@!\$&'\(\)\*\+\\,;=.>< ]+$/i +export const URL_REGEXP = + /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#%[\]@!\$&'\(\)\*\+\\,;=.>< ]+$/i /* PROTOCOL REQUIRED*/ // https://regex101.com/r/pSnDC7/1 -export const URL_REGEXP_BACKEND = /^((https?):\/\/)[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#%[\]@!\$&'\(\)\*\+\\,;=.>< ]+$/i +export const URL_REGEXP_BACKEND = + /^((https?):\/\/)[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#%[\]@!\$&'\(\)\*\+\\,;=.>< ]+$/i // https://www.regextester.com/96577 export const ILLEGAL_NAME_CHARACTERS_REGEXP = /([@\$!])/ // https://regex101.com/r/aoHxNo/1 @@ -51,11 +56,14 @@ export const HAS_NUMBER = /(?=.*[0-9]).*/ // https://regex101.com/r/NNIuKQ/1 export const HAS_LETTER_OR_SYMBOL = /(?=.*[^\d\s]).*/ // https://regex101.com/r/gznzc6/1 strips params for OJS links -export const REDIRECT_URI_REGEXP = /(?=redirect_uri=)(.*?)(?=orcidapi)|(?=redirect_uri=)(.*?)$/ +export const REDIRECT_URI_REGEXP = + /(?=redirect_uri=)(.*?)(?=orcidapi)|(?=redirect_uri=)(.*?)$/ //https://regex101.com/r/yjxqUa/1 -export const AMOUNT_FORMATTED_WITH_DECIMAL_REGEXP = /^(\d+(\.\d{1,2})?|\.?\d{1,2})$/ +export const AMOUNT_FORMATTED_WITH_DECIMAL_REGEXP = + /^(\d+(\.\d{1,2})?|\.?\d{1,2})$/ //https://regex101.com/r/pIQDel/1 -export const AMOUNT_FULLY_FORMATTED_REGEX = /(?=.*\d)^(([1-9]\d{0,2}(,\d{3})*)|0)?(\.\d{1,2})?$/ +export const AMOUNT_FULLY_FORMATTED_REGEX = + /(?=.*\d)^(([1-9]\d{0,2}(,\d{3})*)|0)?(\.\d{1,2})?$/ //https://regex101.com/r/EAlANV/1 export const AMOUNT_DIGITS_ONLY_REGEX = /^\d+$/ // https://regex101.com/r/XvbCrA/1 @@ -345,3 +353,11 @@ export function getAriaLabel( return $localize`:@@shared.dialogAriaLabeledByPeerReview:Manage peer review dialog` } } + +export function navigateTo(val, windowRef) { + if (val === '/signout' && environment.proxyMode) { + this._user.noRedirectLogout().subscribe() + } else { + windowRef.location.href = val + } +} diff --git a/src/app/core/announcer/announcer.service.spec.ts b/src/app/core/announcer/announcer.service.spec.ts new file mode 100644 index 0000000000..dce7470a7c --- /dev/null +++ b/src/app/core/announcer/announcer.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing' + +import { AnnouncerService } from './announcer.service' + +describe('AnnouncerService', () => { + let service: AnnouncerService + + beforeEach(() => { + TestBed.configureTestingModule({}) + service = TestBed.inject(AnnouncerService) + }) + + it('should be created', () => { + expect(service).toBeTruthy() + }) +}) diff --git a/src/app/core/announcer/announcer.service.ts b/src/app/core/announcer/announcer.service.ts new file mode 100644 index 0000000000..f43fabf3f6 --- /dev/null +++ b/src/app/core/announcer/announcer.service.ts @@ -0,0 +1,59 @@ +import { LiveAnnouncer } from '@angular/cdk/a11y' +import { Injectable } from '@angular/core' +import { PageEvent } from '@angular/material/paginator' +import { environment } from 'src/environments/environment' + +@Injectable({ + providedIn: 'root', +}) +export class AnnouncerService { + constructor(private _liveAnnouncer: LiveAnnouncer) {} + youAreOnPageLabel = $localize`:@@shared.youAreOnPage:You are on page` + ofLabel = $localize`:@@shared.of:of` + thereAreLabel = $localize`:@@shared.thereAre:There are` + onEachPageLabel = $localize`:@@shared.onEachPage:on each page` + showingLabel = $localize`:@@shared.showing:Showing` + lastAnnouncement: string + + liveAnnouncePagination(paginatorLabel: PageEvent, itemType: string) { + const announcement = + this.youAreOnPageLabel + + ' ' + + (paginatorLabel.pageIndex + 1) + + ' ' + + this.ofLabel + + ' ' + + // Total pages + Math.ceil(paginatorLabel.length / paginatorLabel.pageSize) + + '. ' + + this.thereAreLabel + + ' ' + + paginatorLabel.pageSize + + ' ' + + itemType + + ' ' + + this.onEachPageLabel + + '. ' + + this.showingLabel + + ' ' + + paginatorLabel.pageSize + + ' ' + + this.ofLabel + + ' ' + + paginatorLabel.length + + ' ' + + itemType + + if (this.lastAnnouncement !== announcement) { + this.lastAnnouncement = announcement + this.announce(announcement) + } + } + + private announce(announcement: string) { + if (environment.debugger) { + console.debug('📢' + announcement) + } + this._liveAnnouncer.announce(announcement, 'assertive') + } +} diff --git a/src/app/core/google-analytics/google-analytics.service.spec.ts b/src/app/core/google-analytics/google-analytics.service.spec.ts index 3bf971bce1..563b9c5cf7 100644 --- a/src/app/core/google-analytics/google-analytics.service.spec.ts +++ b/src/app/core/google-analytics/google-analytics.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@angular/core/testing' -import { GoogleAnalyticsService } from './google-analytics.service' +import { GoogleUniversalAnalyticsService } from './google-universal-analytics.service' import { WINDOW_PROVIDERS } from '../../cdk/window' describe('GoogleAnalyticsService', () => { @@ -11,8 +11,8 @@ describe('GoogleAnalyticsService', () => { ) it('should be created', () => { - const service: GoogleAnalyticsService = TestBed.inject( - GoogleAnalyticsService + const service: GoogleUniversalAnalyticsService = TestBed.inject( + GoogleUniversalAnalyticsService ) expect(service).toBeTruthy() }) diff --git a/src/app/core/google-analytics/google-analytics.service.ts b/src/app/core/google-analytics/google-analytics.service.ts deleted file mode 100644 index 911974f489..0000000000 --- a/src/app/core/google-analytics/google-analytics.service.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Inject, Injectable } from '@angular/core' -import { Observable } from 'rxjs' -import { WINDOW } from 'src/app/cdk/window' -import { PerformanceMarks } from 'src/app/constants' -import { RequestInfoForm } from 'src/app/types' -import { environment } from 'src/environments/environment' -// @ts-ignore -import Gtag from 'gtag.js' - -@Injectable({ - providedIn: 'root', -}) -export class GoogleAnalyticsService { - gtag: Gtag - constructor(@Inject(WINDOW) private window: Window) { - this.gtag = (window as any).gtag - } - - reportPageView(url: string) { - if (environment.debugger) { - console.debug(`GA - Navigation ${url}`) - } - this.gtag('config', environment.GOOGLE_ANALYTICS, { - cookie_flags: 'SameSite=None;Secure', - page_path: url, - page_location: window.location.href, - anonymize_ip: true, - sample_rate: '100', - site_speed_sample_rate: environment.GOOGLE_ANALYTICS_TESTING_MODE - ? '100' - : '1', - }) - } - - reportNavigationStart(url: string) { - this.startPerformanceMeasurement(url) - } - - reportNavigationEnd(url: string) { - const duration = this.finishPerformanceMeasurement(url) - if (duration) { - if (environment.debugger) { - console.debug(`GA - Took ${duration} to load ${url}`) - } - this.gtag('event', 'timing_complete', { - name: this.removeUrlParameters(url), - value: Math.round(duration), - event_category: 'angular_navigation', - page_location: url, - }) - } - } - - reportError(description: string, fatal = false) { - console.error(` -__Report error GA__ -description: "${description}" -fatal: "${fatal}" - `) - - this.gtag('event', 'exception', { - description, - fatal, - }) - } - - public reportEvent( - action: string, - event_category: string, - event_label: RequestInfoForm | string - ): Observable { - // if has RequestInfoForm add the client string as event_label - if (typeof event_label !== 'string') { - event_label = 'OAuth ' + this.buildClientString(event_label) - } - if (environment.debugger) { - console.debug(`GA - Event /${event_category}/${action}/${event_label}/`) - } - return this.eventObservable(action, { - event_category, - event_label, - }) - } - - removeUrlParameters(url: string) { - return url.split('?')[0] - } - - private startPerformanceMeasurement(url: string): void { - if (this.window.performance) { - this.window.performance.mark(PerformanceMarks.navigationStartPrefix + url) - } - } - - private finishPerformanceMeasurement(url: string): number | void { - if (this.window.performance) { - this.window.performance.mark(PerformanceMarks.navigationEndPrefix + url) - let timeForNavigation - this.window.performance.measure( - url, - PerformanceMarks.navigationStartPrefix + url, - PerformanceMarks.navigationEndPrefix + url - ) - this.window.performance.getEntriesByName(url).forEach((value) => { - timeForNavigation = value.duration - }) - this.clearPerformanceMarks(url) - return timeForNavigation - } - } - - private clearPerformanceMarks(url: string) { - if (this.window.performance) { - this.window.performance.clearMarks( - PerformanceMarks.navigationStartPrefix + url - ) - this.window.performance.clearMarks( - PerformanceMarks.navigationEndPrefix + url - ) - this.window.performance.clearMeasures(url) - } - } - - // see https://medium.com/wizdm-genesys/using-gtag-in-angular-b99a10025fcd - private eventObservable( - action: string, - params?: Gtag.EventParams - ): Observable { - // Wraps the event call into a Promise - return new Observable((observer) => { - try { - // Triggers a 3s time-out timer - const tmr = setTimeout( - () => observer.error(new Error('gtag call timed-out')), - 3000 - ) - // Performs the event call resolving with the event callback - this.gtag('event', action, { - ...params, - event_callback: () => { - clearTimeout(tmr) - observer.next() - observer.complete() - }, - }) - } catch (e) { - // Rejects the promise on errors - observer.error(e) - } - }) - } - - buildClientString(request: RequestInfoForm) { - return request.memberName + ' - ' + request.clientName - } -} diff --git a/src/app/core/google-analytics/google-universal-analytics.service.ts b/src/app/core/google-analytics/google-universal-analytics.service.ts new file mode 100644 index 0000000000..059b044b35 --- /dev/null +++ b/src/app/core/google-analytics/google-universal-analytics.service.ts @@ -0,0 +1,94 @@ +import { Injectable } from '@angular/core' +import { Observable } from 'rxjs' +import { RequestInfoForm } from 'src/app/types' +import { environment } from 'src/environments/environment' +import { + buildClientString, + pushOnDataLayer, + removeUrlParameters, +} from '../../analytics-utils' +import { ItemGTM } from '../../types/item_gtm' + +@Injectable({ + providedIn: 'root', +}) +export class GoogleUniversalAnalyticsService { + constructor() {} + + reportPageView(url: string) { + if (environment.debugger) { + console.debug(`GA - Navigation ${url}`) + } + + const item: ItemGTM = { + event: 'pageview', + page: { + path: url, + location: window.location.href, + }, + } + + return this.pushTag(item).subscribe() + } + + reportNavigationEnd(url: string, duration: number | void): Observable { + if (duration) { + if (environment.debugger) { + console.debug(`GA - Took ${duration} to load ${url}`) + } + + const item: ItemGTM = { + event: 'timing_complete', + eventProps: { + name: removeUrlParameters(url), + value: Math.round(duration), + category: 'angular_navigation', + page_location: url, + }, + } + + return this.pushTag(item) + } + } + + reportError(description: string, fatal = false) { + console.error(` +__Report error GA__ +description: "${description}" +fatal: "${fatal}" + `) + } + + public reportEvent( + action: string, + event_category: string, + event_label: RequestInfoForm | string + ): Observable { + // if has RequestInfoForm add the client string as event_label + if (typeof event_label !== 'string') { + event_label = 'OAuth ' + buildClientString(event_label) + } + if (environment.debugger) { + console.debug(`GA - Event /${event_category}/${action}/${event_label}/`) + } + + const item: ItemGTM = { + event: 'event', + eventProps: { + category: event_category, + action: action, + label: event_label, + }, + } + + return this.pushTag(item) + } + + public pushTag(item: ItemGTM): Observable { + return new Observable((subscriber) => { + pushOnDataLayer(item) + subscriber.next() + subscriber.complete() + }) + } +} diff --git a/src/app/core/google-tag-manager/google-tag-manager.service.spec.ts b/src/app/core/google-tag-manager/google-tag-manager.service.spec.ts new file mode 100644 index 0000000000..0c57dbc7fd --- /dev/null +++ b/src/app/core/google-tag-manager/google-tag-manager.service.spec.ts @@ -0,0 +1,88 @@ +import { inject, TestBed, waitForAsync } from '@angular/core/testing' +import { GoogleTagManagerService } from './google-tag-manager.service' +import { ItemGTM } from '../../types/item_gtm' +import { ErrorHandlerService } from '../error-handler/error-handler.service' +import { PlatformInfoService } from '../../cdk/platform-info' +import { SnackbarService } from '../../cdk/snackbar/snackbar.service' +import { MatSnackBar } from '@angular/material/snack-bar' +import { MatDialog } from '@angular/material/dialog' +import { Overlay } from '@angular/cdk/overlay' +import { RouterTestingModule } from '@angular/router/testing' +import { WINDOW_PROVIDERS } from '../../cdk/window' + +describe('GoogleTagManagerService', () => { + let service: GoogleTagManagerService + const tag: ItemGTM = { event: 'page' } + + let browserGlobals = { + windowRef(): any { + return window + }, + documentRef(): any { + return document + }, + } + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + providers: [ + WINDOW_PROVIDERS, + GoogleTagManagerService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }) + browserGlobals.windowRef().dataLayer = [] + })) + + it('should be created', inject( + [GoogleTagManagerService], + (service: GoogleTagManagerService) => { + expect(service).toBeTruthy() + } + )) + + it('should be able to add script element and throw error because cannot connect with GTM', inject( + [GoogleTagManagerService], + (service: GoogleTagManagerService) => { + service.addGtmToDom().subscribe( + () => { + const script = document.querySelector('#GTM') + expect(script).toBeTruthy() + expect(script.getAttribute('src')).toContain( + 'https://www.googletagmanager.com/gtm.js?id=' + ) + }, + (error) => { + expect(error).toEqual({ + name: 'GTM - Error', + message: 'Unable to add GTM', + }) + } + ) + } + )) + + it('should be push tags in the dataLayer', inject( + [GoogleTagManagerService], + (service: GoogleTagManagerService) => { + return service.pushTag(tag).subscribe( + () => { + expect(browserGlobals.windowRef().dataLayer[1]).toEqual(tag) + expect(browserGlobals.windowRef().dataLayer[2]).toBeFalsy() + }, + (error) => { + expect(error).toEqual({ + name: 'GTM - Error', + message: 'Unable to add GTM', + }) + } + ) + } + )) +}) diff --git a/src/app/core/google-tag-manager/google-tag-manager.service.ts b/src/app/core/google-tag-manager/google-tag-manager.service.ts new file mode 100644 index 0000000000..6cb2f0cae5 --- /dev/null +++ b/src/app/core/google-tag-manager/google-tag-manager.service.ts @@ -0,0 +1,149 @@ +import { Injectable } from '@angular/core' +import { environment } from '../../../environments/environment' +import { RequestInfoForm } from '../../types' +import { BehaviorSubject, Observable } from 'rxjs' +import { + browserGlobals, + buildClientString, + pushOnDataLayer, + removeUrlParameters, +} from '../../analytics-utils' +import { catchError } from 'rxjs/operators' +import { ItemGTM } from '../../types/item_gtm' +import { ERROR_REPORT } from '../../errors' +import { ErrorHandlerService } from '../error-handler/error-handler.service' + +@Injectable({ + providedIn: 'root', +}) +export class GoogleTagManagerService { + private _loading = new BehaviorSubject(false) + public readonly loading$ = this._loading.asObservable() + + private isLoaded = false + + constructor(private _errorHandler: ErrorHandlerService) {} + + public pushTag(item: ItemGTM): Observable { + return new Observable((subscriber) => { + if (!this.isLoaded) { + this.addGtmToDom() + .pipe( + catchError((err) => + this._errorHandler.handleError( + err, + ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA + ) + ) + ) + .subscribe( + (response) => { + if (response) { + pushOnDataLayer(item) + subscriber.next() + subscriber.complete() + } + }, + () => + subscriber.error({ + name: 'GTM - Error', + message: 'Unable to add GTM', + }) + ) + } else { + pushOnDataLayer(item) + subscriber.next() + subscriber.complete() + } + }) + } + + public addGtmToDom(): Observable { + return new Observable((subscriber) => { + if (this.isLoaded) { + subscriber.next(true) + subscriber.complete() + } + const doc = browserGlobals.documentRef() + pushOnDataLayer({ + 'gtm.start': new Date().getTime(), + event: 'gtm.js', + }) + + const gtmScript = doc.createElement('script') + gtmScript.id = 'GTM' + gtmScript.async = true + gtmScript.src = + 'https://www.googletagmanager.com/gtm.js?id=' + + environment.GOOGLE_TAG_MANAGER + gtmScript.addEventListener('load', () => { + this.isLoaded = true + subscriber.next(true) + subscriber.complete() + }) + gtmScript.addEventListener('error', () => { + subscriber.error({ + name: 'GTM - Error', + message: 'Unable to add GTM', + }) + }) + doc.head.insertBefore(gtmScript, doc.head.firstChild) + }) + } + + reportPageView(url: string) { + if (environment.debugger) { + console.debug(`GTM Navigation ${url}`) + } + const gtmTag: ItemGTM = { + event: 'page', + pageName: url, + } + this.pushTag(gtmTag).subscribe() + } + + public reportEvent( + event: string, + label: RequestInfoForm | string + ): Observable { + let clientId + let redirectUrl + if (typeof label !== 'string') { + clientId = (label as unknown as RequestInfoForm).clientId + redirectUrl = (label as unknown as RequestInfoForm).redirectUrl + label = 'OAuth ' + buildClientString(label) + } + if (environment.debugger) { + console.debug(`GTM - Event /${event}/${label}/`) + } + + let tagItem = { + event, + label, + } as ItemGTM + if (clientId) { + tagItem.clientId = clientId + } + + if (event === 'Reauthorize') { + tagItem.redirectUrl = redirectUrl + } + + return this.pushTag(tagItem) + } + + reportNavigationEnd(url: string, duration: number | void): Observable { + if (duration) { + if (environment.debugger) { + console.debug(`GTM - Took ${duration} to load ${url}`) + } + + return this.pushTag({ + event: 'timing_complete', + orcid: removeUrlParameters(url), + duration: Math.round(duration), + pageName: url, + }) + } + } +} diff --git a/src/app/core/help-hero/help-hero.service.ts b/src/app/core/help-hero/help-hero.service.ts index 814531211e..ccd75b466b 100644 --- a/src/app/core/help-hero/help-hero.service.ts +++ b/src/app/core/help-hero/help-hero.service.ts @@ -15,9 +15,10 @@ import { environment } from 'src/environments/environment' export class HelpHeroService { hlp: HelpHero - private getNumberOfValidatedEmails( - emails: AssertionVisibilityString[] - ): { numberOfValidatedEmails: number; numberOfUnvalidatedEmails: number } { + private getNumberOfValidatedEmails(emails: AssertionVisibilityString[]): { + numberOfValidatedEmails: number + numberOfUnvalidatedEmails: number + } { return { numberOfValidatedEmails: emails?.filter((email) => { return email.verified === true diff --git a/src/app/core/paginator/matPaginator.service.ts b/src/app/core/paginator/matPaginator.service.ts index 38a071e338..3134ecbb31 100644 --- a/src/app/core/paginator/matPaginator.service.ts +++ b/src/app/core/paginator/matPaginator.service.ts @@ -1,5 +1,7 @@ import { MatPaginatorIntl } from '@angular/material/paginator' import { Injectable } from '@angular/core' +import { BehaviorSubject, Subject } from 'rxjs' +import { LiveAnnouncer } from '@angular/cdk/a11y' @Injectable() export class MatPaginatorIntlImplementation extends MatPaginatorIntl { @@ -9,6 +11,7 @@ export class MatPaginatorIntlImplementation extends MatPaginatorIntl { nextPageLabel = $localize`:@@ngOrcid.material.nextPageLabel:Next page` previousPageLabel = $localize`:@@ngOrcid.material.previousPageLabel:Previous page` ofLabel = $localize`:@@ngOrcid.material.of:of` + pageLabel = $localize`:@@ngOrcid.material.page:Page` /** The following function was taken from / https://github.com/angular/components/blob/dd37ca57406412c1ebeaec56cab5a517f796d4b9/src/material/paginator/paginator-intl.ts @@ -16,20 +19,9 @@ export class MatPaginatorIntlImplementation extends MatPaginatorIntl { /** A label for the range of items within the current page and the length of the whole list. */ getRangeLabel = (page: number, pageSize: number, length: number) => { - if (length === 0 || pageSize === 0) { - return `0 ${this.ofLabel} ${length}` - } + const pageLength = + length === 0 || pageSize === 0 ? 0 : Math.ceil(length / pageSize) - length = Math.max(length, 0) - - const startIndex = page * pageSize - - // If the start index exceeds the list length, do not try and fix the end index to the end. - const endIndex = - startIndex < length - ? Math.min(startIndex + pageSize, length) - : startIndex + pageSize - - return `${startIndex + 1} – ${endIndex} ${this.ofLabel} ${length}` + return ` ${this.pageLabel} ${page + 1} ${this.ofLabel} ${pageLength}` } } diff --git a/src/app/core/record-emails/record-emails.service.ts b/src/app/core/record-emails/record-emails.service.ts index 66256d7b68..567c34493a 100644 --- a/src/app/core/record-emails/record-emails.service.ts +++ b/src/app/core/record-emails/record-emails.service.ts @@ -161,9 +161,10 @@ export class RecordEmailsService { } const value: Assertion = { value: control.value } - const thisEmailIsAlreadyOnTheBackend = emailThatAreAlreadySaveOnTheBackend.filter( - (email) => email.value === value.value - ) + const thisEmailIsAlreadyOnTheBackend = + emailThatAreAlreadySaveOnTheBackend.filter( + (email) => email.value === value.value + ) if (thisEmailIsAlreadyOnTheBackend.length) { return of(null) diff --git a/src/app/core/record-personal-identifiers/record-person-identifier.service.ts b/src/app/core/record-personal-identifiers/record-person-identifier.service.ts index 84728ffdf1..6d82f3be31 100644 --- a/src/app/core/record-personal-identifiers/record-person-identifier.service.ts +++ b/src/app/core/record-personal-identifiers/record-person-identifier.service.ts @@ -44,9 +44,8 @@ export class RecordPersonIdentifierService { options: UserRecordOptions ): ReplaySubject { if (!this.$privatePersonIdentifier) { - this.$privatePersonIdentifier = new ReplaySubject( - 1 - ) + this.$privatePersonIdentifier = + new ReplaySubject(1) } else if (!options.forceReload) { return this.$privatePersonIdentifier } diff --git a/src/app/core/record-public-side-bar/record-public-side-bar.service.ts b/src/app/core/record-public-side-bar/record-public-side-bar.service.ts index 4129ea8ad4..a973b09c3f 100644 --- a/src/app/core/record-public-side-bar/record-public-side-bar.service.ts +++ b/src/app/core/record-public-side-bar/record-public-side-bar.service.ts @@ -36,9 +36,8 @@ export class RecordPublicSideBarService { (options.forceReload && !this.sleepForceReloads) ) { this.sleepForceReloads = true - this.$SideBarPublicUserRecordSubject = new ReplaySubject( - 1 - ) + this.$SideBarPublicUserRecordSubject = + new ReplaySubject(1) setTimeout(() => { this.sleepForceReloads = false }, 100) diff --git a/src/app/core/record-research-resource/record-research-resource.service.ts b/src/app/core/record-research-resource/record-research-resource.service.ts index 1d5c8d2cc4..be6f1c40bf 100644 --- a/src/app/core/record-research-resource/record-research-resource.service.ts +++ b/src/app/core/record-research-resource/record-research-resource.service.ts @@ -17,7 +17,8 @@ import { DEFAULT_PAGE_SIZE } from 'src/app/constants' providedIn: 'root', }) export class RecordResearchResourceService { - $researchResourcesSubject: ReplaySubject = new ReplaySubject() + $researchResourcesSubject: ReplaySubject = + new ReplaySubject() headers = new HttpHeaders({ 'Access-Control-Allow-Origin': '*', diff --git a/src/app/core/record/record.service.ts b/src/app/core/record/record.service.ts index dbc426a5c7..1081d746be 100644 --- a/src/app/core/record/record.service.ts +++ b/src/app/core/record/record.service.ts @@ -183,7 +183,8 @@ export class RecordService { countries: countries as CountriesEndpoint, keyword: keyword as KeywordEndPoint, website: website as WebsitesEndPoint, - externalIdentifier: externalIdentifier as PersonIdentifierEndpoint, + externalIdentifier: + externalIdentifier as PersonIdentifierEndpoint, names: names as NamesEndPoint, biography: biography as BiographyEndPoint, affiliations: affiliations as AffiliationUIGroup[], diff --git a/src/app/core/register/register.backend-validators.ts b/src/app/core/register/register.backend-validators.ts index 7197172337..f3597f7535 100644 --- a/src/app/core/register/register.backend-validators.ts +++ b/src/app/core/register/register.backend-validators.ts @@ -142,9 +142,8 @@ export function RegisterBackendValidatorMixin< return ( formGroup: UntypedFormGroup ): Observable => { - const value: RegisterForm = this.formGroupToPasswordRegisterForm( - formGroup - ) + const value: RegisterForm = + this.formGroupToPasswordRegisterForm(formGroup) if (value.password.value === '' || value.passwordConfirm.value === '') { return of(null) } diff --git a/src/app/core/register/register.form-adapter.ts b/src/app/core/register/register.form-adapter.ts index 85e23e2018..8657261895 100644 --- a/src/app/core/register/register.form-adapter.ts +++ b/src/app/core/register/register.form-adapter.ts @@ -8,9 +8,9 @@ export function RegisterFormAdapterMixin>(base: T) { formGroupToEmailRegisterForm(formGroup: UntypedFormGroup): RegisterForm { let additionalEmailsValue: Value[] if (formGroup.controls['additionalEmails']) { - const additionalEmailsControls = (formGroup.controls[ - 'additionalEmails' - ] as UntypedFormGroup).controls + const additionalEmailsControls = ( + formGroup.controls['additionalEmails'] as UntypedFormGroup + ).controls additionalEmailsValue = Object.keys(additionalEmailsControls) .filter((name) => additionalEmailsControls[name].value !== '') .map((name) => { diff --git a/src/app/core/user/user.service.ts b/src/app/core/user/user.service.ts index e024abc8f8..aa6682e98c 100644 --- a/src/app/core/user/user.service.ts +++ b/src/app/core/user/user.service.ts @@ -1,5 +1,5 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' -import { Injectable } from '@angular/core' +import { Inject, Injectable } from '@angular/core' import { BehaviorSubject, combineLatest, @@ -26,6 +26,7 @@ import { tap, } from 'rxjs/operators' import { PlatformInfoService } from 'src/app/cdk/platform-info' +import { WINDOW } from 'src/app/cdk/window' import { ERROR_REPORT } from 'src/app/errors' import { NameForm, @@ -62,7 +63,8 @@ export class UserService { private _platform: PlatformInfoService, private _oauth: OauthService, private _disco: DiscoService, - private _userInfo: UserInfoService + private _userInfo: UserInfoService, + @Inject(WINDOW) private window: Window ) {} $userStatusChecked = new ReplaySubject() private currentlyLoggedIn: boolean @@ -494,4 +496,21 @@ export class UserService { ) } } + + noRedirectLogout() { + return this._http + .get(`${environment.API_WEB}signout`, { + headers: this.headers, + observe: 'response', + responseType: 'text', + }) + .pipe( + tap( + () => {}, + () => { + this.window.location.reload() + } + ) + ) + } } diff --git a/src/app/guards/authorize.guard.ts b/src/app/guards/authorize.guard.ts index ea71a7ad4b..4bca7c0e7d 100644 --- a/src/app/guards/authorize.guard.ts +++ b/src/app/guards/authorize.guard.ts @@ -6,27 +6,31 @@ import { RouterStateSnapshot, UrlTree, } from '@angular/router' -import { NEVER, Observable, of } from 'rxjs' -import { catchError, map, switchMap } from 'rxjs/operators' +import { forkJoin, NEVER, Observable, of, throwError, timer } from 'rxjs' +import { catchError, map, switchMap, take, tap, timeout } from 'rxjs/operators' import { PlatformInfoService } from '../cdk/platform-info' import { WINDOW } from '../cdk/window' import { UserService } from '../core' import { ErrorHandlerService } from '../core/error-handler/error-handler.service' -import { GoogleAnalyticsService } from '../core/google-analytics/google-analytics.service' +import { GoogleUniversalAnalyticsService } from '../core/google-analytics/google-universal-analytics.service' import { ERROR_REPORT } from '../errors' import { RequestInfoForm } from '../types' +import { GoogleTagManagerService } from '../core/google-tag-manager/google-tag-manager.service' @Injectable({ providedIn: 'root', }) export class AuthorizeGuard implements CanActivateChild { + lastRedirectUrl: string + redirectTroughGtmWasCalled: boolean constructor( private _user: UserService, private _router: Router, private _platform: PlatformInfoService, @Inject(WINDOW) private window: Window, - private _gtag: GoogleAnalyticsService, + private _gtag: GoogleUniversalAnalyticsService, + private _googleTagManagerService: GoogleTagManagerService, private _errorHandler: ErrorHandlerService ) {} canActivateChild( @@ -34,6 +38,7 @@ export class AuthorizeGuard implements CanActivateChild { state: RouterStateSnapshot ): Observable | UrlTree | boolean { return this._user.getUserSession().pipe( + take(1), switchMap((session) => { const oauthSession = session.oauthSession if (session.userInfo?.LOCKED === 'true') { @@ -49,10 +54,7 @@ export class AuthorizeGuard implements CanActivateChild { oauthSession.responseType && oauthSession.redirectUrl.includes(oauthSession.responseType + '=') ) { - return this.reportAlreadyAuthorize(session.oauthSession).pipe( - catchError(() => this.sendUserToRedirectURL(oauthSession)), - switchMap(() => this.sendUserToRedirectURL(oauthSession)) - ) + return this.reportAlreadyAuthorize(oauthSession) } else if ( oauthSession.forceLogin || !session.oauthSessionIsLoggedIn @@ -65,22 +67,54 @@ export class AuthorizeGuard implements CanActivateChild { ) } - sendUserToRedirectURL(oauthSession: RequestInfoForm) { + sendUserToRedirectURL(oauthSession: RequestInfoForm): Observable { this.window.location.href = oauthSession.redirectUrl return NEVER } reportAlreadyAuthorize(request: RequestInfoForm) { - return this._gtag - .reportEvent(`Reauthorize`, 'RegGrowth', request) - .pipe( - catchError((err) => - this._errorHandler.handleError( - err, - ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA - ) + const analyticsReports: Observable[] = [] + analyticsReports.push( + this._gtag.reportEvent(`Reauthorize`, 'RegGrowth', request) + ) + analyticsReports.push( + this._googleTagManagerService.reportEvent(`Reauthorize`, request) + ) + addEventListener('beforeunload', (event) => { + // temporally keeping the console logs to debug the issue + console.log('beforeunload', event) + this.redirectTroughGtmWasCalled = true + }) + + return forkJoin(analyticsReports).pipe( + tap( + (value) => { + console.log('reportAlreadyAuthorize tap', value) + }, + (err) => { + console.log('reportAlreadyAuthorize tap err', err) + } + ), + catchError((err) => { + this._errorHandler.handleError( + err, + ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA ) - ) + return this.sendUserToRedirectURL(request) + }), + // If and Add blocker like Ublock is enable the GTM `redirectURL` will never be called + // This add blockers will also not trigger the `catchError` above, since GTM will not throw an error + // So this timeout will redirect the user to the redirectURL after 4 seconds of waiting for the GTM redirect + switchMap(() => timer(4000)), + switchMap(() => { + // Checks that a redirect by GTM is not already in progress + // Stop a second redirect to happen if the a browser event `beforeunload` event was triggered + if (this.redirectTroughGtmWasCalled) { + return NEVER + } + return this.sendUserToRedirectURL(request) + }) + ) } private redirectToLoginPage( diff --git a/src/app/guards/sign-in.guard.ts b/src/app/guards/sign-in.guard.ts index b7c8eac8df..f9f4d75860 100644 --- a/src/app/guards/sign-in.guard.ts +++ b/src/app/guards/sign-in.guard.ts @@ -7,7 +7,7 @@ import { UrlTree, } from '@angular/router' import { Observable } from 'rxjs' -import { map } from 'rxjs/operators' +import { map, take } from 'rxjs/operators' import { PlatformInfoService } from '../cdk/platform-info' import { WINDOW } from '../cdk/window' @@ -33,6 +33,7 @@ export class SignInGuard implements CanActivateChild { const queryParams = next.queryParams return this._user.getUserSession().pipe( + take(1), map((session) => { if (session.oauthSession) { if (queryParams.email || queryParams.orcid) { diff --git a/src/app/guards/third-party-signin-completed.guard.ts b/src/app/guards/third-party-signin-completed.guard.ts index fa4c114be4..11ad6ee1b9 100644 --- a/src/app/guards/third-party-signin-completed.guard.ts +++ b/src/app/guards/third-party-signin-completed.guard.ts @@ -6,13 +6,16 @@ import { RouterStateSnapshot, UrlTree, } from '@angular/router' -import { Observable } from 'rxjs' -import { first, map } from 'rxjs/operators' +import { forkJoin, Observable } from 'rxjs' +import { catchError, first, map } from 'rxjs/operators' import { WINDOW } from '../cdk/window' import { UserService } from '../core' -import { GoogleAnalyticsService } from '../core/google-analytics/google-analytics.service' +import { GoogleUniversalAnalyticsService } from '../core/google-analytics/google-universal-analytics.service' import { RequestInfoForm } from '../types' +import { GoogleTagManagerService } from '../core/google-tag-manager/google-tag-manager.service' +import { ERROR_REPORT } from '../errors' +import { ErrorHandlerService } from '../core/error-handler/error-handler.service' @Injectable({ providedIn: 'root', @@ -21,7 +24,9 @@ export class ThirdPartySigninCompletedGuard implements CanActivateChild { constructor( private _router: Router, @Inject(WINDOW) private window: Window, - private _analytics: GoogleAnalyticsService, + private _analytics: GoogleUniversalAnalyticsService, + private _googleTagManagerService: GoogleTagManagerService, + private _errorHandler: ErrorHandlerService, private _user: UserService ) {} @@ -37,21 +42,47 @@ export class ThirdPartySigninCompletedGuard implements CanActivateChild { first(), map((value) => value.oauthSession), map((requestInfoForm: RequestInfoForm) => { - this._analytics.reportEvent( - 'Sign-In', - 'RegGrowth', - requestInfoForm || 'Website' // If the is no requestInfoForm report a Website signin]]\ + const analyticsReports: Observable[] = [] + analyticsReports.push( + this._analytics.reportEvent( + 'Sign-In', + 'RegGrowth', + requestInfoForm || 'Website' // If the is no requestInfoForm report a Website signin]]\ + ) + ) + analyticsReports.push( + this._googleTagManagerService.reportEvent( + 'Sign-In', + requestInfoForm || 'Website' + ) ) - if (state.url.startsWith('/my-orcid')) { - // This "out of router navigation" is necesary while my-orcid exist on the old page - // as a temporal side effect will show the new app header/footer before navigating into the old app - ;(this.window).outOfRouterNavigation('/my-orcid') - return false - } else { - return this._router.parseUrl( - state.url.replace(/\/third-party-signin-completed/, '') + this._googleTagManagerService + .addGtmToDom() + .pipe( + catchError((err) => + this._errorHandler.handleError( + err, + ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA + ) + ) ) - } + .subscribe((response) => { + if (response) { + forkJoin(analyticsReports) + .pipe( + catchError((err) => + this._errorHandler.handleError( + err, + ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA + ) + ) + ) + .subscribe() + } + }) + return this._router.parseUrl( + state.url.replace(/\/third-party-signin-completed/, '') + ) }) ) } diff --git a/src/app/inbox/components/notification-permission-institutional-connection/notification-permission-institutional-connection.component.ts b/src/app/inbox/components/notification-permission-institutional-connection/notification-permission-institutional-connection.component.ts index d90fca3210..55fce6a6a0 100644 --- a/src/app/inbox/components/notification-permission-institutional-connection/notification-permission-institutional-connection.component.ts +++ b/src/app/inbox/components/notification-permission-institutional-connection/notification-permission-institutional-connection.component.ts @@ -12,7 +12,8 @@ import { InboxNotificationInstitutional } from 'src/app/types/notifications.endp preserveWhitespaces: true, }) export class NotificationPermissionInstitutionalConnectionComponent - implements OnInit { + implements OnInit +{ _notification: InboxNotificationInstitutional @Input() set notification(notification: InboxNotificationInstitutional) { diff --git a/src/app/inbox/components/notification/notification.component.ts b/src/app/inbox/components/notification/notification.component.ts index 6b0b9b4afe..a168b9abc7 100644 --- a/src/app/inbox/components/notification/notification.component.ts +++ b/src/app/inbox/components/notification/notification.component.ts @@ -44,7 +44,8 @@ import { ], }) export class NotificationComponent - implements OnInit, AfterViewInit, ControlValueAccessor { + implements OnInit, AfterViewInit, ControlValueAccessor +{ state = 'close' @ViewChild('header') header: ElementRef @HostBinding('class.archived') _archived = false diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index 7f1886fddd..0893f2e909 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -49,7 +49,7 @@ export class HeaderComponent implements OnInit { _userInfo: UserService, _togglz: TogglzService, location: Location, - private _signingService: SignInService + private _user: UserService ) { _router.events .pipe(filter((event: any) => event instanceof NavigationStart)) @@ -275,6 +275,10 @@ export class HeaderComponent implements OnInit { } navigateTo(val) { - ;(this.window as any).outOfRouterNavigation(val) + if (val === '/signout' && environment.proxyMode) { + this._user.noRedirectLogout().subscribe() + } else { + ;(this.window as any).outOfRouterNavigation(val) + } } } diff --git a/src/app/layout/user-menu/user-menu.component.ts b/src/app/layout/user-menu/user-menu.component.ts index b177370c04..3b65f718fb 100644 --- a/src/app/layout/user-menu/user-menu.component.ts +++ b/src/app/layout/user-menu/user-menu.component.ts @@ -32,7 +32,7 @@ export class UserMenuComponent implements OnInit { constructor( private _router: Router, - _userInfo: UserService, + private _userInfo: UserService, @Inject(WINDOW) private window: Window, _platform: PlatformInfoService, private _inboxService: InboxService, @@ -84,6 +84,10 @@ export class UserMenuComponent implements OnInit { } navigateTo(val) { - this.window.location.href = val + if (val === '/signout' && environment.proxyMode) { + this._userInfo.noRedirectLogout().subscribe() + } else { + this.window.location.href = val + } } } diff --git a/src/app/record/components/affiliation-stack/affiliation-stack.component.ts b/src/app/record/components/affiliation-stack/affiliation-stack.component.ts index 624bcaf362..9ef0ae6a9a 100644 --- a/src/app/record/components/affiliation-stack/affiliation-stack.component.ts +++ b/src/app/record/components/affiliation-stack/affiliation-stack.component.ts @@ -126,8 +126,8 @@ export class AffiliationStackComponent implements OnInit { */ toggleDetails(affiliation: Affiliation) { const putCode = affiliation.putCode.value - this.panelDetailsState[putCode].state = !this.panelDetailsState[putCode] - .state + this.panelDetailsState[putCode].state = + !this.panelDetailsState[putCode].state if (this.panelDetailsState[putCode].state) { this.getMoreDetailsAndOrganizationDisambiguatedFromTheServer( @@ -147,15 +147,15 @@ export class AffiliationStackComponent implements OnInit { ): Observable<[false | OrgDisambiguated, AffiliationUIGroup[]]> { const putCode = affiliation.putCode.value - let $affiliationDisambiguationSource: Observable< - false | OrgDisambiguated - > = of(false) + let $affiliationDisambiguationSource: Observable = + of(false) // Adds call for disambiguationSource if the affiliation has if (affiliation.disambiguationSource) { - $affiliationDisambiguationSource = this._organizationsService.getOrgDisambiguated( - affiliation.disambiguationSource.value, - affiliation.disambiguatedAffiliationSourceId.value - ) + $affiliationDisambiguationSource = + this._organizationsService.getOrgDisambiguated( + affiliation.disambiguationSource.value, + affiliation.disambiguatedAffiliationSourceId.value + ) } const $affiliationDetails = this._affiliationService.getAffiliationsDetails( affiliation.affiliationType.value, diff --git a/src/app/record/components/affiliation-stacks-groups/affiliation-stacks-groups.component.spec.ts b/src/app/record/components/affiliation-stacks-groups/affiliation-stacks-groups.component.spec.ts index 64fdacec90..937ff1e19c 100644 --- a/src/app/record/components/affiliation-stacks-groups/affiliation-stacks-groups.component.spec.ts +++ b/src/app/record/components/affiliation-stacks-groups/affiliation-stacks-groups.component.spec.ts @@ -17,25 +17,23 @@ describe('AffiliationStacksGroupsComponent', () => { let component: AffiliationStacksGroupsComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule], - declarations: [AffiliationStacksGroupsComponent], - providers: [ - WINDOW_PROVIDERS, - RecordService, - RecordAffiliationService, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [AffiliationStacksGroupsComponent], + providers: [ + WINDOW_PROVIDERS, + RecordService, + RecordAffiliationService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(AffiliationStacksGroupsComponent) diff --git a/src/app/record/components/affiliation-stacks-groups/modals/modal-affiliations/modal-affiliations.component.html b/src/app/record/components/affiliation-stacks-groups/modals/modal-affiliations/modal-affiliations.component.html index 9ff085ba51..a4055dbc95 100644 --- a/src/app/record/components/affiliation-stacks-groups/modals/modal-affiliations/modal-affiliations.component.html +++ b/src/app/record/components/affiliation-stacks-groups/modals/modal-affiliations/modal-affiliations.component.html @@ -102,7 +102,7 @@
    - +

    - -
    +
    -
    -
    -
    -
    -

    +
    +
    +

    + - - Employment details - - - Qualification details - - - Education details - - - Invited Position details - - - Distinction details - - - Membership details - - - Service details - - * -

    -
    + Employment details + + + Qualification details + + + Education details + + + Invited Position details + + + Distinction details + + + Membership details + + + Service details + + * +

    -
    - +
    +
    -
    @@ -818,10 +815,9 @@

    Funding agency

    fundingForm.get('country').hasError('required') && fundingForm.get('country').touched }" + id="funding-country-location" > - Country/Location + Country/Location *
    @@ -839,7 +835,7 @@

    Funding agency

    formControlName="country" > {{ countryCode.key }} @@ -857,7 +853,7 @@

    Funding agency

    -
    +
    @@ -929,7 +925,7 @@

    -
    +
    diff --git a/src/app/record/components/funding-stacks-groups/modals/modal-funding/modal-funding.component.spec.ts b/src/app/record/components/funding-stacks-groups/modals/modal-funding/modal-funding.component.spec.ts index 005a96fc6e..668c185886 100644 --- a/src/app/record/components/funding-stacks-groups/modals/modal-funding/modal-funding.component.spec.ts +++ b/src/app/record/components/funding-stacks-groups/modals/modal-funding/modal-funding.component.spec.ts @@ -36,41 +36,39 @@ describe('ModalFundingComponent', () => { let component: ModalFundingComponent let fixture: ComponentFixture - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [ - FormsModule, - HttpClientTestingModule, - MatAutocompleteModule, - MatFormFieldModule, - MatIconModule, - MatInputModule, - MatSelectModule, - ModalModule, - NoopAnimationsModule, - PrivacySelectorModule, - ReactiveFormsModule, - RecordModule, - RouterTestingModule, - SharedModule, - ], - declarations: [ModalFundingComponent], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - WINDOW_PROVIDERS, - UntypedFormBuilder, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - Overlay, - PlatformInfoService, - UserService, - ], - }).compileComponents() - }) - ) + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + HttpClientTestingModule, + MatAutocompleteModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatSelectModule, + ModalModule, + NoopAnimationsModule, + PrivacySelectorModule, + ReactiveFormsModule, + RecordModule, + RouterTestingModule, + SharedModule, + ], + declarations: [ModalFundingComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + WINDOW_PROVIDERS, + UntypedFormBuilder, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + Overlay, + PlatformInfoService, + UserService, + ], + }).compileComponents() + })) beforeEach(() => { fixture = TestBed.createComponent(ModalFundingComponent) diff --git a/src/app/record/components/funding-stacks-groups/modals/modal-funding/modal-funding.component.ts b/src/app/record/components/funding-stacks-groups/modals/modal-funding/modal-funding.component.ts index 96571814a4..f00325d13d 100644 --- a/src/app/record/components/funding-stacks-groups/modals/modal-funding/modal-funding.component.ts +++ b/src/app/record/components/funding-stacks-groups/modals/modal-funding/modal-funding.component.ts @@ -126,6 +126,9 @@ export class ModalFundingComponent implements OnInit, OnDestroy { ariaLabelEndDate = $localize`:@@shared.startDate:End date` ariaLabelSaveChanges = $localize`:@@shared.saveFundingChanges:Save funding changes` ariaLabelCancelChanges = $localize`:@@shared.cancelFundingChanges:Cancel changes and close Funding` + ariaLabelClose = $localize`:@@shared.closeFunding:Close funding` + ariaLabelDescription = $localize`:@@shared.descriptionFunding:Project description` + ariaLabelAmount = $localize`:@@shared.amountFunding:Amount` ngOrcidYear = $localize`:@@shared.year:Year` ngOrcidMonth = $localize`:@@shared.month:Month` @@ -133,7 +136,6 @@ export class ModalFundingComponent implements OnInit, OnDestroy { ngOrcidSelectLanguage = $localize`:@@shared.selectLanguage:Select a language` ngOrcidSelectACountryOrLocation = $localize`:@@shared.selectACountryOrLocation:Select a country or location` ngOrcidDefaultVisibilityLabel = $localize`:@@shared.visibilityDescription:Control who can see this information by setting the visibility. Your default visibility is` - constructor( @Inject(WINDOW) private window: Window, private _platform: PlatformInfoService, @@ -367,10 +369,10 @@ export class ModalFundingComponent implements OnInit, OnDestroy { if (this.funding.fundingTitle?.translatedTitle) { this.fundingForm.patchValue({ translatedTitleGroup: { - translatedTitleContent: this.funding.fundingTitle?.translatedTitle - .content, - translatedTitleLanguage: this.funding.fundingTitle?.translatedTitle - .languageCode, + translatedTitleContent: + this.funding.fundingTitle?.translatedTitle.content, + translatedTitleLanguage: + this.funding.fundingTitle?.translatedTitle.languageCode, }, }) } @@ -410,18 +412,22 @@ export class ModalFundingComponent implements OnInit, OnDestroy { this.region = this.funding.region?.value this.country = this.funding.country?.value this.fundingType = this.funding.fundingType?.value - this.fundingSubtype = this.funding.organizationDefinedFundingSubType?.subtype?.value + this.fundingSubtype = + this.funding.organizationDefinedFundingSubType?.subtype?.value this.fundingProjectTitle = this.funding.fundingTitle?.title.value - this.translatedTitleContent = this.funding.fundingTitle?.translatedTitle?.content - this.translatedTitleLanguage = this.funding.fundingTitle?.translatedTitle?.languageCode + this.translatedTitleContent = + this.funding.fundingTitle?.translatedTitle?.content + this.translatedTitleLanguage = + this.funding.fundingTitle?.translatedTitle?.languageCode this.fundingProjectLink = this.funding.url?.value this.description = this.funding.description?.value this.currencyCode = this.funding.currencyCode?.value this.amount = this.funding.amount?.value - this.disambiguatedFundingSourceId = this.funding.disambiguatedFundingSourceId?.value + this.disambiguatedFundingSourceId = + this.funding.disambiguatedFundingSourceId?.value this.disambiguatedFundingSource = this.funding.disambiguationSource?.value - this.showTranslationTitle = !!this.funding.fundingTitle?.translatedTitle - ?.content + this.showTranslationTitle = + !!this.funding.fundingTitle?.translatedTitle?.content this.agencyName = { value: this.funding.fundingName.value, } as Organization diff --git a/src/app/record/components/org-identifier/org-identifier.component.html b/src/app/record/components/org-identifier/org-identifier.component.html index 0f94be1dd6..dcedaa2ac7 100644 --- a/src/app/record/components/org-identifier/org-identifier.component.html +++ b/src/app/record/components/org-identifier/org-identifier.component.html @@ -16,11 +16,11 @@

    rel="noopener noreferrer" href="{{ orgDisambiguated.sourceType - | organizationLink: orgDisambiguated.sourceId + | organizationLink : orgDisambiguated.sourceId }}" *ngIf=" orgDisambiguated.sourceType - | organizationLink: orgDisambiguated.sourceId + | organizationLink : orgDisambiguated.sourceId | isUrlWithProtocol " > @@ -30,7 +30,7 @@

    *ngIf=" !( orgDisambiguated.sourceType - | organizationLink: orgDisambiguated.sourceId + | organizationLink : orgDisambiguated.sourceId | isUrlWithProtocol ) " @@ -77,10 +77,10 @@

    class="underline" target="_blank" rel="noopener noreferrer" - href="{{ org.identifierType | organizationLink: org.preferred }}" + href="{{ org.identifierType | organizationLink : org.preferred }}" *ngIf=" org.identifierType - | organizationLink: org.preferred + | organizationLink : org.preferred | isUrlWithProtocol " > @@ -90,7 +90,7 @@

    *ngIf=" !( org.identifierType - | organizationLink: org.preferred + | organizationLink : org.preferred | isUrlWithProtocol ) " @@ -106,12 +106,12 @@

    >{{ id }} @@ -119,7 +119,7 @@

    class="underline" target="_blank" rel="noopener noreferrer" - href="{{ org.identifierType | organizationLink: id }}" + href="{{ org.identifierType | organizationLink : id }}" > {{ id }} diff --git a/src/app/record/components/research-resource-stack/research-resource-stack.component.html b/src/app/record/components/research-resource-stack/research-resource-stack.component.html index f3a7c258a7..ebe6621d26 100644 --- a/src/app/record/components/research-resource-stack/research-resource-stack.component.html +++ b/src/app/record/components/research-resource-stack/research-resource-stack.component.html @@ -54,6 +54,7 @@

    (topPanelOfTheStackModeChange)="changeTopPanelOfTheStack(research)" [type]="'research-resources'" [item]="research" + [panelTitle]="research.title" > diff --git a/src/app/record/components/research-resource-stacks-group/research-resource-stacks-group.component.ts b/src/app/record/components/research-resource-stacks-group/research-resource-stacks-group.component.ts index b1ecd57304..5ad7cd5ba7 100644 --- a/src/app/record/components/research-resource-stacks-group/research-resource-stacks-group.component.ts +++ b/src/app/record/components/research-resource-stacks-group/research-resource-stacks-group.component.ts @@ -15,8 +15,10 @@ import { ResearchResourcesEndpoint, ResearchResourcesGroup, } from '../../../types/record-research-resources.endpoint' -import { PageEvent } from '@angular/material/paginator' +import { MatPaginatorIntl, PageEvent } from '@angular/material/paginator' import { DEFAULT_PAGE_SIZE } from 'src/app/constants' +import { LiveAnnouncer } from '@angular/cdk/a11y' +import { AnnouncerService } from 'src/app/core/announcer/announcer.service' @Component({ selector: 'app-research-resources', @@ -62,13 +64,16 @@ export class ResearchResourceStacksGroupComponent implements OnInit { paginationIndex: number paginationPageSize: number paginationLoading = true + paginatorLabel: string constructor( _platform: PlatformInfoService, private _organizationsService: OrganizationsService, private _record: RecordService, private _recordResearchResourceService: RecordResearchResourceService, - private _user: UserService + private _user: UserService, + private _matPaginatorIntl: MatPaginatorIntl, + private _announcer: AnnouncerService ) { _platform .get() @@ -110,6 +115,14 @@ export class ResearchResourceStacksGroupComponent implements OnInit { this.userRecordContext.offset = event.pageIndex * event.pageSize this.userRecordContext.pageSize = event.pageSize this.userRecordContext.publicRecordId = this.isPublicRecord + + this.paginatorLabel = this._matPaginatorIntl.getRangeLabel( + event.pageIndex, + event.pageSize, + event.length + ) + this._announcer.liveAnnouncePagination(event, this.regionResearchResources) + this.loadResearchResources() } diff --git a/src/app/record/components/research-resource/research-resource.component.html b/src/app/record/components/research-resource/research-resource.component.html index d412f5a855..71b3b8fad3 100644 --- a/src/app/record/components/research-resource/research-resource.component.html +++ b/src/app/record/components/research-resource/research-resource.component.html @@ -66,7 +66,9 @@ i18n="@@shared.showMoreDetail" [attr.aria-label]=" 'research-resource' - | appPanelActivityActionAriaLabel: 'show':researchResource.title + | appPanelActivityActionAriaLabel + : 'show' + : researchResource.title " > Show more detail @@ -79,7 +81,9 @@ id="show-less-button" [attr.aria-label]=" 'research-resource' - | appPanelActivityActionAriaLabel: 'hide':researchResource.title + | appPanelActivityActionAriaLabel + : 'hide' + : researchResource.title " > Show less detail @@ -208,7 +212,9 @@

    id="show-more-item-button" [attr.aria-label]=" 'research-resource' - | appPanelActivityActionAriaLabel: 'show':item.resourceName + | appPanelActivityActionAriaLabel + : 'show' + : item.resourceName " > Show more detail @@ -221,7 +227,9 @@

    id="show-less-item-button" [attr.aria-label]=" 'research-resource' - | appPanelActivityActionAriaLabel: 'hide':item.resourceName + | appPanelActivityActionAriaLabel + : 'hide' + : item.resourceName " > Show less detail diff --git a/src/app/record/components/top-bar-verification-email/modals/top-bar-verification-email-modal/top-bar-verification-email-modal.component.html b/src/app/record/components/top-bar-verification-email/modals/top-bar-verification-email-modal/top-bar-verification-email-modal.component.html index 8088f7ca08..2be9c7962a 100644 --- a/src/app/record/components/top-bar-verification-email/modals/top-bar-verification-email-modal/top-bar-verification-email-modal.component.html +++ b/src/app/record/components/top-bar-verification-email/modals/top-bar-verification-email-modal/top-bar-verification-email-modal.component.html @@ -57,6 +57,7 @@ rel="noopener noreferrer" target="_blank" i18n="@@topBar.knowledge" + [attr.aria-label]="ariaLabelKnowledgeBase" >knowledge base https://support.orcid.org/hc/en-us/requests/new diff --git a/src/app/record/components/top-bar-verification-email/modals/top-bar-verification-email-modal/top-bar-verification-email-modal.component.ts b/src/app/record/components/top-bar-verification-email/modals/top-bar-verification-email-modal/top-bar-verification-email-modal.component.ts index 1d644e9adb..a5dd4c2e2d 100644 --- a/src/app/record/components/top-bar-verification-email/modals/top-bar-verification-email-modal/top-bar-verification-email-modal.component.ts +++ b/src/app/record/components/top-bar-verification-email/modals/top-bar-verification-email-modal/top-bar-verification-email-modal.component.ts @@ -16,7 +16,11 @@ import { ModalComponent } from '../../../../../cdk/modal/modal/modal.component' preserveWhitespaces: true, }) export class TopBarVerificationEmailModalComponent - implements OnInit, OnDestroy { + implements OnInit, OnDestroy +{ + ariaLabelKnowledgeBase = $localize`:@@side-bar.ariaLabelOrcidTermsOfUseBase:ORCID knowledge base (Opens in a new tab)` + ariaLabelKnowledgeSupport = $localize`:@@side-bar.ariaLabelOrcidTermsSupport:ORCID support page (Opens in a new tab)` + $destroy: Subject = new Subject() primaryEmail: string diff --git a/src/app/record/components/top-bar-verification-email/top-bar-verification-email.component.html b/src/app/record/components/top-bar-verification-email/top-bar-verification-email.component.html index e69cd16b9d..e5ad2fc3e0 100644 --- a/src/app/record/components/top-bar-verification-email/top-bar-verification-email.component.html +++ b/src/app/record/components/top-bar-verification-email/top-bar-verification-email.component.html @@ -1,4 +1,5 @@ @@ -386,14 +389,13 @@

    " > add_circle_outline diff --git a/src/app/record/components/work-contributors/work-contributors.component.spec.ts b/src/app/record/components/work-contributors/work-contributors.component.spec.ts index 283e09d973..466f336e8c 100644 --- a/src/app/record/components/work-contributors/work-contributors.component.spec.ts +++ b/src/app/record/components/work-contributors/work-contributors.component.spec.ts @@ -108,8 +108,9 @@ describe('WorkContributorsComponent', () => { const creditNameAndRoles = fixture.debugElement.query( By.css('.credit-name-and-roles') ).nativeElement - const affiliation = fixture.debugElement.query(By.css('.affiliation')) - .nativeElement + const affiliation = fixture.debugElement.query( + By.css('.affiliation') + ).nativeElement expect(creditNameAndRoles.innerHTML).not.toBeNull() expect(creditNameAndRoles.textContent).toBe( @@ -131,8 +132,9 @@ describe('WorkContributorsComponent', () => { const recordHolderData = fixture.debugElement.query( By.css('.credit-name-and-roles') ).nativeElement - const affiliation = fixture.debugElement.query(By.css('.affiliation')) - .nativeElement + const affiliation = fixture.debugElement.query( + By.css('.affiliation') + ).nativeElement expect(recordHolderData.innerHTML).not.toBeNull() expect(recordHolderData.textContent).toBe( @@ -144,7 +146,7 @@ describe('WorkContributorsComponent', () => { it('should display `Add another contributor button`', () => { expect( - debugElement.query(By.css('#cy-add-another-contributor')) + debugElement.query(By.css('.cy-add-another-contributor')) ).toBeTruthy() }) @@ -152,7 +154,7 @@ describe('WorkContributorsComponent', () => { await findByClassNameAndClickButton( debugElement, fixture, - '#cy-add-another-contributor' + '.cy-add-another-contributor' ) const contributorName = await loader.getHarness(MatInputHarness) @@ -166,7 +168,7 @@ describe('WorkContributorsComponent', () => { await findByClassNameAndClickButton( debugElement, fixture, - '#cy-add-another-contributor' + '.cy-add-another-contributor' ) fixture.detectChanges() @@ -174,7 +176,7 @@ describe('WorkContributorsComponent', () => { await findByClassNameAndClickButton( debugElement, fixture, - '#cy-add-another-role-0000-0000-0000-000X' + '.cy-add-another-role' ) const roles = await loader.getAllHarnesses(MatSelectHarness) @@ -186,7 +188,7 @@ describe('WorkContributorsComponent', () => { await findByClassNameAndClickButton( debugElement, fixture, - '#cy-add-another-contributor' + '.cy-add-another-contributor' ) fixture.detectChanges() @@ -194,7 +196,7 @@ describe('WorkContributorsComponent', () => { await findByClassNameAndClickButton( debugElement, fixture, - '#cy-add-another-role-0000-0000-0000-000X' + '.cy-add-another-role' ) const roles = await loader.getAllHarnesses(MatSelectHarness) @@ -219,9 +221,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') expect(contributors.length).toBe(2) expect(contributors[0].querySelector('.orcid-logo')).toBeTruthy() @@ -234,9 +235,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') expect(contributors.length).toBe(2) expect(contributors[0].querySelector('.orcid-logo')).toBeTruthy() @@ -252,9 +252,8 @@ describe('WorkContributorsComponent', () => { const deleteButton = await loader.getHarness(MatButtonHarness) await deleteButton.click() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') expect(contributors.length).toBe(1) }) @@ -263,7 +262,7 @@ describe('WorkContributorsComponent', () => { await findByClassNameAndClickButton( debugElement, fixture, - '#cy-add-another-contributor' + '.cy-add-another-contributor' ) fixture.detectChanges() @@ -271,7 +270,7 @@ describe('WorkContributorsComponent', () => { await findByClassNameAndClickButton( debugElement, fixture, - '#cy-add-another-role-0000-0000-0000-000X' + '.cy-add-another-role' ) const roles = await loader.getAllHarnesses(MatSelectHarness) @@ -300,9 +299,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const noticePanel = fixture.nativeElement.querySelector('.notice-panel') @@ -320,9 +318,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const noticePanel = fixture.nativeElement.querySelector('.notice-panel') @@ -340,9 +337,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const creditNameAndRoles = contributors[1].querySelector( '.credit-name-and-roles' ) @@ -358,9 +354,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const creditNameAndRoles = contributors[1].querySelector( '.credit-name-and-roles' ) @@ -378,9 +373,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const creditNameAndRoles = contributors[1].querySelector( '.credit-name-and-roles' ) @@ -396,9 +390,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const creditNameAndRoles = contributors[1].querySelector( '.credit-name-and-roles' ) @@ -416,9 +409,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const creditNameAndRoles = contributors[1].querySelector( '.credit-name-and-roles' ) @@ -431,7 +423,7 @@ describe('WorkContributorsComponent', () => { it('should display notice panel if 50+ contributors are added and button `Add another contributor` must be disabled', async () => { const addAnotherContributor = debugElement.query( - By.css('#cy-add-another-contributor') + By.css('.cy-add-another-contributor') ) await Array.from({ length: 49 }, (x, i) => { @@ -440,9 +432,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() }) - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const noticePanel = fixture.nativeElement.querySelector('.notice-panel') @@ -470,9 +461,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const noticePanel = fixture.nativeElement.querySelector('.notice-panel') @@ -499,7 +489,7 @@ describe('WorkContributorsComponent', () => { const noticePanel = fixture.nativeElement.querySelector('.notice-panel ') const addAnotherContributor = debugElement.query( - By.css('#cy-add-another-contributor') + By.css('.cy-add-another-contributor') ) const counter = fixture.nativeElement.querySelector('.title ') @@ -521,12 +511,11 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ).nativeElement + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box').nativeElement const addOtherContributor = debugElement.query( - By.css('#cy-add-another-contributor') + By.css('.cy-add-another-contributor') ) expect(contributors).not.toBeTruthy() @@ -570,9 +559,8 @@ describe('WorkContributorsComponent', () => { fixture.detectChanges() - const contributors = fixture.nativeElement.querySelectorAll( - '.contributors-box' - ) + const contributors = + fixture.nativeElement.querySelectorAll('.contributors-box') const addRecordHolderAsContributor = debugElement.query( By.css('#cy-add-record-holder-contributor') diff --git a/src/app/record/components/work-contributors/work-contributors.component.ts b/src/app/record/components/work-contributors/work-contributors.component.ts index 43b03a8b6d..978708b089 100644 --- a/src/app/record/components/work-contributors/work-contributors.component.ts +++ b/src/app/record/components/work-contributors/work-contributors.component.ts @@ -1,10 +1,14 @@ import { ChangeDetectorRef, Component, + ElementRef, Inject, Input, OnDestroy, OnInit, + QueryList, + ViewChildren, + ViewContainerRef, } from '@angular/core' import { AbstractControl, @@ -30,6 +34,7 @@ import { MAX_LENGTH_LESS_THAN_ONE_HUNDRED } from '../../../constants' import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop' import { WINDOW } from '../../../cdk/window' import { TogglzService } from '../../../core/togglz/togglz.service' +import { MatSelect } from '@angular/material/select' @Component({ selector: 'app-work-contributors', @@ -43,7 +48,12 @@ import { TogglzService } from '../../../core/togglz/togglz.service' ], }) export class WorkContributorsComponent implements OnInit, OnDestroy { + @ViewChildren('roleSelect', { read: ViewContainerRef }) + inputs: QueryList + deleteLabel = $localize`:@@shared.deleteActivityAriaLabel:Delete` + @ViewChildren('contributorNames') + contributorNamesInputs: QueryList $destroy: Subject = new Subject() @@ -122,6 +132,9 @@ export class WorkContributorsComponent implements OnInit, OnDestroy { if (!addAnotherContributorStatus) { this.contributorsFormArray.push(this.getContributorForm()) } + this._changeDetectorRef.detectChanges() + + this.contributorNamesInputs.last.nativeElement.focus() } addRecordHolderAsContributor(): void { @@ -143,13 +156,35 @@ export class WorkContributorsComponent implements OnInit, OnDestroy { return (contributor.get('roles') as UntypedFormArray).controls } - addAnotherRole(contributor): void { + addAnotherRole(contributor, contributorIndex?): void { ;(contributor.get('roles') as UntypedFormArray).push( this.getRoleForm( this.workService.getContributionRoleByKey('no specified role').key, false ) ) + this._changeDetectorRef.detectChanges() + if (contributorIndex) { + // Find las input with id contributor and focus on it + const contributorIndexInputs = this.inputs.toArray().filter((input) => { + // Get input native element + const nativeElement = ((input as any).element as ElementRef) + .nativeElement + + // Check if nativeElement has class + if ( + nativeElement.classList.contains( + 'contributor-role-' + contributorIndex + ) + ) { + return input + } + return false + }) + ;( + contributorIndexInputs[contributorIndexInputs.length - 1] as any + ).element.nativeElement.focus() + } } deleteContributor(contributorIndex: number): void { @@ -287,8 +322,8 @@ export class WorkContributorsComponent implements OnInit, OnDestroy { .filter((formGroup) => formGroup.disabled) .map((formGroup) => { const role = formGroup?.value?.role - const translation = this.workService.getContributionRoleByKey(role) - ?.translation + const translation = + this.workService.getContributionRoleByKey(role)?.translation return translation ? translation : role.charAt(0).toUpperCase() + role.slice(1) diff --git a/src/app/record/components/work-external-identifiers-edit/work-external-identifiers-edit.component.html b/src/app/record/components/work-external-identifiers-edit/work-external-identifiers-edit.component.html index 3fdc6d92f9..bdcf3ba223 100644 --- a/src/app/record/components/work-external-identifiers-edit/work-external-identifiers-edit.component.html +++ b/src/app/record/components/work-external-identifiers-edit/work-external-identifiers-edit.component.html @@ -15,7 +15,7 @@
    - - +
    - - +
    -
    -
    +
    +

    Work details

    @@ -22,6 +23,7 @@
    @@ -125,16 +126,16 @@ > - Add translated title + Show translated title - Hide translation title + Hide translated title @@ -149,6 +150,7 @@
    @@ -206,6 +206,7 @@
    @@ -260,10 +259,12 @@
    -
    @@ -286,13 +287,14 @@
    -
    -
    @@ -321,14 +323,13 @@ >
    @@ -518,10 +520,9 @@ [ngClass]="{ error: workForm.hasError('citationType', 'citationGroup') }" + id="citationType" > - Citation type + Citation type
    @@ -561,10 +562,9 @@ [ngClass]="{ error: workForm.hasError('citation', 'citationGroup') }" + id="citation-label" > - Citation + Citation
    -
    @@ -633,7 +637,7 @@
    +
    @@ -742,7 +746,7 @@ [recordHolderAsContributor]="work?.putCode?.value || externalIdentifier" >
    -
    +
    @@ -764,7 +768,7 @@
    -
    -
    +
    -
    +

    Visibility

    diff --git a/src/app/record/components/work-form/work-form/work-form.component.ts b/src/app/record/components/work-form/work-form/work-form.component.ts index 5c1e34da4f..8e3cfadc10 100644 --- a/src/app/record/components/work-form/work-form/work-form.component.ts +++ b/src/app/record/components/work-form/work-form/work-form.component.ts @@ -64,6 +64,8 @@ export class WorkFormComponent implements OnInit { ngOrcidYear = $localize`:@@shared.year:Year` ngOrcidMonth = $localize`:@@shared.month:Month` ngOrcidDay = $localize`:@@shared.day:Day` + languageLabelAriaLabel = $localize`:@@shared.languageLabelAriaLabel:Select the language used in this form` + selectCountryLocationLabel = $localize`:@@shared.selectCountryLocationLabel:Select a country or location of publication` @Input() work: Work @Input() userRecord: UserRecord @@ -606,9 +608,11 @@ export class WorkFormComponent implements OnInit { Object.keys(formErrors).length === 1 && formErrors.workIdentifiers?.length ) { - return (formErrors.workIdentifiers as { - [key: string]: { [key: string]: boolean } - }[]).every((workIdentifiersErrorList) => { + return ( + formErrors.workIdentifiers as { + [key: string]: { [key: string]: boolean } + }[] + ).every((workIdentifiersErrorList) => { return ( // Either workIdentifiers is null // OR it only contains allow error like unResolved or validFormat diff --git a/src/app/record/components/work-modal/work-modal.component.html b/src/app/record/components/work-modal/work-modal.component.html index 05370ab117..1e7d32c366 100644 --- a/src/app/record/components/work-modal/work-modal.component.html +++ b/src/app/record/components/work-modal/work-modal.component.html @@ -1,5 +1,5 @@ - + Works @@ -9,11 +9,16 @@ + + Citation + + + Identifiers - + - - - Citation - - - Identifiers + Citation - Contributors + Identifiers - Citation + Contributors diff --git a/src/app/record/components/work-stack-group/modals/work-external-id-modal/work-external-id-modal.component.ts b/src/app/record/components/work-stack-group/modals/work-external-id-modal/work-external-id-modal.component.ts index 792b877d2b..3b8d98f1ab 100644 --- a/src/app/record/components/work-stack-group/modals/work-external-id-modal/work-external-id-modal.component.ts +++ b/src/app/record/components/work-stack-group/modals/work-external-id-modal/work-external-id-modal.component.ts @@ -32,7 +32,8 @@ import { UserRecord } from '../../../../../types/record.local' styleUrls: ['./work-external-id-modal.component.scss'], }) export class WorkExternalIdModalComponent - implements OnInit, OnDestroy, AfterViewInit { + implements OnInit, OnDestroy, AfterViewInit +{ $destroy: Subject = new Subject() @ViewChild('workFormComponent') workFormComponent: WorkFormComponent diff --git a/src/app/record/components/work-stack-group/work-stack-group.component.html b/src/app/record/components/work-stack-group/work-stack-group.component.html index 61c6d3a1ec..43e6ef2ecf 100644 --- a/src/app/record/components/work-stack-group/work-stack-group.component.html +++ b/src/app/record/components/work-stack-group/work-stack-group.component.html @@ -39,6 +39,7 @@ [disabled]="paginationLoading" > +
    = new Subject() isMobile: boolean diff --git a/src/app/record/components/work/work.component.html b/src/app/record/components/work/work.component.html index 8412b20aef..fb156d2216 100644 --- a/src/app/record/components/work/work.component.html +++ b/src/app/record/components/work/work.component.html @@ -15,7 +15,7 @@ *ngIf=" togglzWorksContributors && (work?.contributorsGroupedByOrcid - | recordHolderRoles: isPublicRecord:id) as roles + | recordHolderRoles : isPublicRecord : id) as roles " > @@ -37,7 +37,7 @@ @@ -83,7 +83,7 @@ *ngIf="!panelDetailsState?.state" i18n="@@shared.showMoreDetail" [attr.aria-label]=" - 'work' | appPanelActivityActionAriaLabel: 'show':work?.title?.value + 'work' | appPanelActivityActionAriaLabel : 'show' : work?.title?.value " >Show more detail @@ -93,7 +93,7 @@ *ngIf="panelDetailsState?.state" i18n="@@shared.showLessDetail" [attr.aria-label]=" - 'work' | appPanelActivityActionAriaLabel: 'hide':work?.title?.value + 'work' | appPanelActivityActionAriaLabel : 'hide' : work?.title?.value " >Show less detail @@ -194,7 +194,7 @@

    }" *ngFor=" let contributor of work?.contributorsGroupedByOrcid - | slice: 0:maxNumberContributorsWorkDetails; + | slice : 0 : maxNumberContributorsWorkDetails; let index = index " > diff --git a/src/app/record/pages/my-orcid/my-orcid.component.html b/src/app/record/pages/my-orcid/my-orcid.component.html index 9aa5ca893e..aacacf7f8c 100644 --- a/src/app/record/pages/my-orcid/my-orcid.component.html +++ b/src/app/record/pages/my-orcid/my-orcid.component.html @@ -126,7 +126,8 @@ Record last modified - {{ userRecord?.lastModifiedTime | date: 'medium':'UTC' }} UTC + {{ userRecord?.lastModifiedTime | date : 'medium' : 'UTC' }} + UTC

    diff --git a/src/app/register/components/form-anti-robots/form-anti-robots.component.ts b/src/app/register/components/form-anti-robots/form-anti-robots.component.ts index 6a4ad893d5..959ba4876b 100644 --- a/src/app/register/components/form-anti-robots/form-anti-robots.component.ts +++ b/src/app/register/components/form-anti-robots/form-anti-robots.component.ts @@ -32,7 +32,8 @@ import { BaseForm } from '../BaseForm' }) export class FormAntiRobotsComponent extends BaseForm - implements OnInit, DoCheck { + implements OnInit, DoCheck +{ captchaFailState = false captchaLoadedWithWidgetId: number $widgetIdUpdated = new Subject() diff --git a/src/app/register/components/form-personal/form-personal.component.ts b/src/app/register/components/form-personal/form-personal.component.ts index 019255648c..0ec38870eb 100644 --- a/src/app/register/components/form-personal/form-personal.component.ts +++ b/src/app/register/components/form-personal/form-personal.component.ts @@ -43,7 +43,8 @@ import { ReactivationLocal } from '../../../types/reactivation.local' }) export class FormPersonalComponent extends BaseForm - implements OnInit, AfterViewInit { + implements OnInit, AfterViewInit +{ @Input() reactivation: ReactivationLocal @ViewChild('firstInput') firstInput: ElementRef labelInfoAboutName = $localize`:@@register.ariaLabelInfo:info about names` @@ -128,9 +129,8 @@ export class FormPersonalComponent allEmailsAreUnique(): ValidatorFn { return (formGroup: UntypedFormGroup) => { let hasError = false - const registerForm = this._register.formGroupToEmailRegisterForm( - formGroup - ) + const registerForm = + this._register.formGroupToEmailRegisterForm(formGroup) const error = { backendErrors: { additionalEmails: {} } } diff --git a/src/app/register/components/form-terms/form-terms.component.ts b/src/app/register/components/form-terms/form-terms.component.ts index b280ba398f..f0de5ce315 100644 --- a/src/app/register/components/form-terms/form-terms.component.ts +++ b/src/app/register/components/form-terms/form-terms.component.ts @@ -53,9 +53,10 @@ export class FormTermsComponent extends BaseForm implements OnInit, DoCheck { // OVERWRITE registerOnChange(fn: any) { this.form.valueChanges.subscribe((value) => { - const registerForm = this._register.formGroupTermsOfUseAndDataProcessedRegisterForm( - this.form as UntypedFormGroup - ) + const registerForm = + this._register.formGroupTermsOfUseAndDataProcessedRegisterForm( + this.form as UntypedFormGroup + ) fn(registerForm) }) } diff --git a/src/app/register/components/form-visibility/form-visibility.component.ts b/src/app/register/components/form-visibility/form-visibility.component.ts index 37b0c20eca..2dbfc8716a 100644 --- a/src/app/register/components/form-visibility/form-visibility.component.ts +++ b/src/app/register/components/form-visibility/form-visibility.component.ts @@ -32,7 +32,8 @@ import { BaseForm } from '../BaseForm' }) export class FormVisibilityComponent extends BaseForm - implements OnInit, DoCheck { + implements OnInit, DoCheck +{ visibilityOptions = VISIBILITY_OPTIONS errorState = false activitiesVisibilityDefault = new UntypedFormControl('', Validators.required) diff --git a/src/app/register/pages/register/register.component.ts b/src/app/register/pages/register/register.component.ts index 0137ef5bf4..a1f3b89263 100644 --- a/src/app/register/pages/register/register.component.ts +++ b/src/app/register/pages/register/register.component.ts @@ -12,14 +12,14 @@ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms' import { MatDialog } from '@angular/material/dialog' import { MatStep } from '@angular/material/stepper' import { Router } from '@angular/router' -import { combineLatest } from 'rxjs' +import { combineLatest, forkJoin, Observable } from 'rxjs' import { catchError, first, map, switchMap } from 'rxjs/operators' import { IsThisYouComponent } from 'src/app/cdk/is-this-you' import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info' import { WINDOW } from 'src/app/cdk/window' import { isRedirectToTheAuthorizationPage } from 'src/app/constants' import { UserService } from 'src/app/core' -import { GoogleAnalyticsService } from 'src/app/core/google-analytics/google-analytics.service' +import { GoogleUniversalAnalyticsService } from 'src/app/core/google-analytics/google-universal-analytics.service' import { RegisterService } from 'src/app/core/register/register.service' import { RequestInfoForm } from 'src/app/types' import { @@ -33,6 +33,7 @@ import { ThirdPartyAuthData } from 'src/app/types/sign-in-data.endpoint' import { ReactivationLocal } from '../../../types/reactivation.local' import { SearchService } from '../../../core/search/search.service' import { SearchParameters, SearchResults } from 'src/app/types' +import { GoogleTagManagerService } from '../../../core/google-tag-manager/google-tag-manager.service' @Component({ selector: 'app-register', @@ -66,7 +67,8 @@ export class RegisterComponent implements OnInit, AfterViewInit { private _register: RegisterService, private _dialog: MatDialog, @Inject(WINDOW) private window: Window, - private _gtag: GoogleAnalyticsService, + private _gtag: GoogleUniversalAnalyticsService, + private _googleTagManagerService: GoogleTagManagerService, private _user: UserService, private _router: Router, private _errorHandler: ErrorHandlerService, @@ -158,12 +160,22 @@ export class RegisterComponent implements OnInit, AfterViewInit { .subscribe((response) => { this.loading = false if (response.url) { - this._gtag - .reportEvent( + const analyticsReports: Observable[] = [] + + analyticsReports.push( + this._gtag.reportEvent( 'New-Registration', 'RegGrowth', this.requestInfoForm || 'Website' ) + ) + analyticsReports.push( + this._googleTagManagerService.reportEvent( + 'New-Registration', + this.requestInfoForm || 'Website' + ) + ) + forkJoin(analyticsReports) .pipe( catchError((err) => this._errorHandler.handleError( diff --git a/src/app/search/components/advance-search/advance-search.component.html b/src/app/search/components/advance-search/advance-search.component.html index 25146a41f7..8610662947 100644 --- a/src/app/search/components/advance-search/advance-search.component.html +++ b/src/app/search/components/advance-search/advance-search.component.html @@ -1,4 +1,5 @@
    +

    Search