From 376788ea2eca65e23c2e295cb74e13668c41ef4c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 7 Dec 2022 17:04:42 -0600 Subject: [PATCH 01/33] Automated upgrade to Cypress 12. --- cypress.config.ts | 31 +++++++++++++++++++ cypress.json | 25 --------------- .../breadcrumbs.cy.ts} | 2 +- .../browse-by-author.cy.ts} | 0 .../browse-by-dateissued.cy.ts} | 0 .../browse-by-subject.cy.ts} | 0 .../browse-by-title.cy.ts} | 0 .../collection-page.cy.ts} | 2 +- .../collection-statistics.cy.ts} | 2 +- .../community-list.cy.ts} | 0 .../community-page.cy.ts} | 2 +- .../community-statistics.cy.ts} | 2 +- .../footer.spec.ts => e2e/footer.cy.ts} | 0 .../header.spec.ts => e2e/header.cy.ts} | 0 .../homepage-statistics.cy.ts} | 0 .../homepage.spec.ts => e2e/homepage.cy.ts} | 0 .../item-page.spec.ts => e2e/item-page.cy.ts} | 2 +- .../item-statistics.cy.ts} | 2 +- .../login-modal.cy.ts} | 2 +- .../my-dspace.spec.ts => e2e/my-dspace.cy.ts} | 2 +- .../pagenotfound.cy.ts} | 0 .../search-navbar.cy.ts} | 2 +- .../search-page.cy.ts} | 2 +- .../submission.cy.ts} | 4 +-- cypress/support/commands.ts | 2 +- cypress/support/{index.ts => e2e.ts} | 0 package.json | 4 +-- yarn.lock | 26 ++++++++-------- 28 files changed, 59 insertions(+), 55 deletions(-) create mode 100644 cypress.config.ts delete mode 100644 cypress.json rename cypress/{integration/breadcrumbs.spec.ts => e2e/breadcrumbs.cy.ts} (88%) rename cypress/{integration/browse-by-author.spec.ts => e2e/browse-by-author.cy.ts} (100%) rename cypress/{integration/browse-by-dateissued.spec.ts => e2e/browse-by-dateissued.cy.ts} (100%) rename cypress/{integration/browse-by-subject.spec.ts => e2e/browse-by-subject.cy.ts} (100%) rename cypress/{integration/browse-by-title.spec.ts => e2e/browse-by-title.cy.ts} (100%) rename cypress/{integration/collection-page.spec.ts => e2e/collection-page.cy.ts} (88%) rename cypress/{integration/collection-statistics.spec.ts => e2e/collection-statistics.cy.ts} (95%) rename cypress/{integration/community-list.spec.ts => e2e/community-list.cy.ts} (100%) rename cypress/{integration/community-page.spec.ts => e2e/community-page.cy.ts} (88%) rename cypress/{integration/community-statistics.spec.ts => e2e/community-statistics.cy.ts} (95%) rename cypress/{integration/footer.spec.ts => e2e/footer.cy.ts} (100%) rename cypress/{integration/header.spec.ts => e2e/header.cy.ts} (100%) rename cypress/{integration/homepage-statistics.spec.ts => e2e/homepage-statistics.cy.ts} (100%) rename cypress/{integration/homepage.spec.ts => e2e/homepage.cy.ts} (100%) rename cypress/{integration/item-page.spec.ts => e2e/item-page.cy.ts} (94%) rename cypress/{integration/item-statistics.spec.ts => e2e/item-statistics.cy.ts} (95%) rename cypress/{integration/login-modal.spec.ts => e2e/login-modal.cy.ts} (99%) rename cypress/{integration/my-dspace.spec.ts => e2e/my-dspace.cy.ts} (99%) rename cypress/{integration/pagenotfound.spec.ts => e2e/pagenotfound.cy.ts} (100%) rename cypress/{integration/search-navbar.spec.ts => e2e/search-navbar.cy.ts} (98%) rename cypress/{integration/search-page.spec.ts => e2e/search-page.cy.ts} (97%) rename cypress/{integration/submission.spec.ts => e2e/submission.cy.ts} (98%) rename cypress/support/{index.ts => e2e.ts} (100%) diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000000..e45b8a5396 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,31 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({ + videosFolder: 'cypress/videos', + screenshotsFolder: 'cypress/screenshots', + fixturesFolder: 'cypress/fixtures', + retries: { + runMode: 2, + openMode: 0, + }, + env: { + DSPACE_TEST_ADMIN_USER: 'dspacedemo+admin@gmail.com', + DSPACE_TEST_ADMIN_PASSWORD: 'dspace', + DSPACE_TEST_COMMUNITY: '0958c910-2037-42a9-81c7-dca80e3892b4', + DSPACE_TEST_COLLECTION: '282164f5-d325-4740-8dd1-fa4d6d3e7200', + DSPACE_TEST_ENTITY_PUBLICATION: 'e98b0f27-5c19-49a0-960d-eb6ad5287067', + DSPACE_TEST_SEARCH_TERM: 'test', + DSPACE_TEST_SUBMIT_COLLECTION_NAME: 'Sample Collection', + DSPACE_TEST_SUBMIT_COLLECTION_UUID: '9d8334e9-25d3-4a67-9cea-3dffdef80144', + DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com', + DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace', + }, + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require('./cypress/plugins/index.ts')(on, config) + }, + baseUrl: 'http://localhost:4000', + }, +}) diff --git a/cypress.json b/cypress.json deleted file mode 100644 index 3adf7839c2..0000000000 --- a/cypress.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "integrationFolder": "cypress/integration", - "supportFile": "cypress/support/index.ts", - "videosFolder": "cypress/videos", - "screenshotsFolder": "cypress/screenshots", - "pluginsFile": "cypress/plugins/index.ts", - "fixturesFolder": "cypress/fixtures", - "baseUrl": "http://127.0.0.1:4000", - "retries": { - "runMode": 2, - "openMode": 0 - }, - "env": { - "DSPACE_TEST_ADMIN_USER": "dspacedemo+admin@gmail.com", - "DSPACE_TEST_ADMIN_PASSWORD": "dspace", - "DSPACE_TEST_COMMUNITY": "0958c910-2037-42a9-81c7-dca80e3892b4", - "DSPACE_TEST_COLLECTION": "282164f5-d325-4740-8dd1-fa4d6d3e7200", - "DSPACE_TEST_ENTITY_PUBLICATION": "e98b0f27-5c19-49a0-960d-eb6ad5287067", - "DSPACE_TEST_SEARCH_TERM": "test", - "DSPACE_TEST_SUBMIT_COLLECTION_NAME": "Sample Collection", - "DSPACE_TEST_SUBMIT_COLLECTION_UUID": "9d8334e9-25d3-4a67-9cea-3dffdef80144", - "DSPACE_TEST_SUBMIT_USER": "dspacedemo+submit@gmail.com", - "DSPACE_TEST_SUBMIT_USER_PASSWORD": "dspace" - } -} diff --git a/cypress/integration/breadcrumbs.spec.ts b/cypress/e2e/breadcrumbs.cy.ts similarity index 88% rename from cypress/integration/breadcrumbs.spec.ts rename to cypress/e2e/breadcrumbs.cy.ts index 62b9a8ad1d..157cfdce20 100644 --- a/cypress/integration/breadcrumbs.spec.ts +++ b/cypress/e2e/breadcrumbs.cy.ts @@ -1,4 +1,4 @@ -import { TEST_ENTITY_PUBLICATION } from 'cypress/support'; +import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Breadcrumbs', () => { diff --git a/cypress/integration/browse-by-author.spec.ts b/cypress/e2e/browse-by-author.cy.ts similarity index 100% rename from cypress/integration/browse-by-author.spec.ts rename to cypress/e2e/browse-by-author.cy.ts diff --git a/cypress/integration/browse-by-dateissued.spec.ts b/cypress/e2e/browse-by-dateissued.cy.ts similarity index 100% rename from cypress/integration/browse-by-dateissued.spec.ts rename to cypress/e2e/browse-by-dateissued.cy.ts diff --git a/cypress/integration/browse-by-subject.spec.ts b/cypress/e2e/browse-by-subject.cy.ts similarity index 100% rename from cypress/integration/browse-by-subject.spec.ts rename to cypress/e2e/browse-by-subject.cy.ts diff --git a/cypress/integration/browse-by-title.spec.ts b/cypress/e2e/browse-by-title.cy.ts similarity index 100% rename from cypress/integration/browse-by-title.spec.ts rename to cypress/e2e/browse-by-title.cy.ts diff --git a/cypress/integration/collection-page.spec.ts b/cypress/e2e/collection-page.cy.ts similarity index 88% rename from cypress/integration/collection-page.spec.ts rename to cypress/e2e/collection-page.cy.ts index a0140d8faf..be7ef8d182 100644 --- a/cypress/integration/collection-page.spec.ts +++ b/cypress/e2e/collection-page.cy.ts @@ -1,4 +1,4 @@ -import { TEST_COLLECTION } from 'cypress/support'; +import { TEST_COLLECTION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Collection Page', () => { diff --git a/cypress/integration/collection-statistics.spec.ts b/cypress/e2e/collection-statistics.cy.ts similarity index 95% rename from cypress/integration/collection-statistics.spec.ts rename to cypress/e2e/collection-statistics.cy.ts index 90b569c824..58601f1c4e 100644 --- a/cypress/integration/collection-statistics.spec.ts +++ b/cypress/e2e/collection-statistics.cy.ts @@ -1,4 +1,4 @@ -import { TEST_COLLECTION } from 'cypress/support'; +import { TEST_COLLECTION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Collection Statistics Page', () => { diff --git a/cypress/integration/community-list.spec.ts b/cypress/e2e/community-list.cy.ts similarity index 100% rename from cypress/integration/community-list.spec.ts rename to cypress/e2e/community-list.cy.ts diff --git a/cypress/integration/community-page.spec.ts b/cypress/e2e/community-page.cy.ts similarity index 88% rename from cypress/integration/community-page.spec.ts rename to cypress/e2e/community-page.cy.ts index 79e21431ad..080dccf820 100644 --- a/cypress/integration/community-page.spec.ts +++ b/cypress/e2e/community-page.cy.ts @@ -1,4 +1,4 @@ -import { TEST_COMMUNITY } from 'cypress/support'; +import { TEST_COMMUNITY } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Community Page', () => { diff --git a/cypress/integration/community-statistics.spec.ts b/cypress/e2e/community-statistics.cy.ts similarity index 95% rename from cypress/integration/community-statistics.spec.ts rename to cypress/e2e/community-statistics.cy.ts index cbf1783c0b..4eb1d7935d 100644 --- a/cypress/integration/community-statistics.spec.ts +++ b/cypress/e2e/community-statistics.cy.ts @@ -1,4 +1,4 @@ -import { TEST_COMMUNITY } from 'cypress/support'; +import { TEST_COMMUNITY } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Community Statistics Page', () => { diff --git a/cypress/integration/footer.spec.ts b/cypress/e2e/footer.cy.ts similarity index 100% rename from cypress/integration/footer.spec.ts rename to cypress/e2e/footer.cy.ts diff --git a/cypress/integration/header.spec.ts b/cypress/e2e/header.cy.ts similarity index 100% rename from cypress/integration/header.spec.ts rename to cypress/e2e/header.cy.ts diff --git a/cypress/integration/homepage-statistics.spec.ts b/cypress/e2e/homepage-statistics.cy.ts similarity index 100% rename from cypress/integration/homepage-statistics.spec.ts rename to cypress/e2e/homepage-statistics.cy.ts diff --git a/cypress/integration/homepage.spec.ts b/cypress/e2e/homepage.cy.ts similarity index 100% rename from cypress/integration/homepage.spec.ts rename to cypress/e2e/homepage.cy.ts diff --git a/cypress/integration/item-page.spec.ts b/cypress/e2e/item-page.cy.ts similarity index 94% rename from cypress/integration/item-page.spec.ts rename to cypress/e2e/item-page.cy.ts index 6a454b678d..f55cfc4b4d 100644 --- a/cypress/integration/item-page.spec.ts +++ b/cypress/e2e/item-page.cy.ts @@ -1,5 +1,5 @@ import { Options } from 'cypress-axe'; -import { TEST_ENTITY_PUBLICATION } from 'cypress/support'; +import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Item Page', () => { diff --git a/cypress/integration/item-statistics.spec.ts b/cypress/e2e/item-statistics.cy.ts similarity index 95% rename from cypress/integration/item-statistics.spec.ts rename to cypress/e2e/item-statistics.cy.ts index 66ebc228db..4fa90d4ef8 100644 --- a/cypress/integration/item-statistics.spec.ts +++ b/cypress/e2e/item-statistics.cy.ts @@ -1,4 +1,4 @@ -import { TEST_ENTITY_PUBLICATION } from 'cypress/support'; +import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Item Statistics Page', () => { diff --git a/cypress/integration/login-modal.spec.ts b/cypress/e2e/login-modal.cy.ts similarity index 99% rename from cypress/integration/login-modal.spec.ts rename to cypress/e2e/login-modal.cy.ts index fece28b425..431237c9fb 100644 --- a/cypress/integration/login-modal.spec.ts +++ b/cypress/e2e/login-modal.cy.ts @@ -1,4 +1,4 @@ -import { TEST_ADMIN_PASSWORD, TEST_ADMIN_USER, TEST_ENTITY_PUBLICATION } from 'cypress/support'; +import { TEST_ADMIN_PASSWORD, TEST_ADMIN_USER, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; const page = { openLoginMenu() { diff --git a/cypress/integration/my-dspace.spec.ts b/cypress/e2e/my-dspace.cy.ts similarity index 99% rename from cypress/integration/my-dspace.spec.ts rename to cypress/e2e/my-dspace.cy.ts index 48f44eecb9..d6fa707f82 100644 --- a/cypress/integration/my-dspace.spec.ts +++ b/cypress/e2e/my-dspace.cy.ts @@ -1,5 +1,5 @@ import { Options } from 'cypress-axe'; -import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support'; +import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('My DSpace page', () => { diff --git a/cypress/integration/pagenotfound.spec.ts b/cypress/e2e/pagenotfound.cy.ts similarity index 100% rename from cypress/integration/pagenotfound.spec.ts rename to cypress/e2e/pagenotfound.cy.ts diff --git a/cypress/integration/search-navbar.spec.ts b/cypress/e2e/search-navbar.cy.ts similarity index 98% rename from cypress/integration/search-navbar.spec.ts rename to cypress/e2e/search-navbar.cy.ts index babd9b9dfd..b13f585323 100644 --- a/cypress/integration/search-navbar.spec.ts +++ b/cypress/e2e/search-navbar.cy.ts @@ -1,4 +1,4 @@ -import { TEST_SEARCH_TERM } from 'cypress/support'; +import { TEST_SEARCH_TERM } from 'cypress/support/e2e'; const page = { fillOutQueryInNavBar(query) { diff --git a/cypress/integration/search-page.spec.ts b/cypress/e2e/search-page.cy.ts similarity index 97% rename from cypress/integration/search-page.spec.ts rename to cypress/e2e/search-page.cy.ts index 623c370c56..8078c4a969 100644 --- a/cypress/integration/search-page.spec.ts +++ b/cypress/e2e/search-page.cy.ts @@ -1,5 +1,5 @@ import { Options } from 'cypress-axe'; -import { TEST_SEARCH_TERM } from 'cypress/support'; +import { TEST_SEARCH_TERM } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Search Page', () => { diff --git a/cypress/integration/submission.spec.ts b/cypress/e2e/submission.cy.ts similarity index 98% rename from cypress/integration/submission.spec.ts rename to cypress/e2e/submission.cy.ts index 9eef596b02..010513dea5 100644 --- a/cypress/integration/submission.spec.ts +++ b/cypress/e2e/submission.cy.ts @@ -1,6 +1,4 @@ -import { Options } from 'cypress-axe'; -import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support'; -import { testA11y } from 'cypress/support/utils'; +import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support/e2e'; describe('New Submission page', () => { // NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 04c217aa0f..63d3992298 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -4,7 +4,7 @@ // *********************************************** import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; -import { FALLBACK_TEST_REST_BASE_URL } from '.'; +import { FALLBACK_TEST_REST_BASE_URL } from './e2e'; // Declare Cypress namespace to help with Intellisense & code completion in IDEs // ALL custom commands MUST be listed here for code completion to work diff --git a/cypress/support/index.ts b/cypress/support/e2e.ts similarity index 100% rename from cypress/support/index.ts rename to cypress/support/e2e.ts diff --git a/package.json b/package.json index 9249884c8e..47ef1dfc0b 100644 --- a/package.json +++ b/package.json @@ -163,8 +163,8 @@ "compression-webpack-plugin": "^9.2.0", "copy-webpack-plugin": "^6.4.1", "cross-env": "^7.0.3", - "cypress": "9.7.0", - "cypress-axe": "^0.14.0", + "cypress": "^12.0.1", + "cypress-axe": "^1.1.0", "deep-freeze": "0.0.1", "eslint": "^8.2.0", "eslint-plugin-deprecation": "^1.3.2", diff --git a/yarn.lock b/yarn.lock index 2fd4fa3bc8..306d870ff1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4255,15 +4255,15 @@ custom-event@~1.0.0: resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== -cypress-axe@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.14.0.tgz#5f5e70fb36b8cb3ba73a8ba01e9262ff1268d5e2" - integrity sha512-7Rdjnko0MjggCmndc1wECAkvQBIhuy+DRtjF7bd5YPZRFvubfMNvrxfqD8PWQmxm7MZE0ffS4Xr43V6ZmvLopg== +cypress-axe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.1.0.tgz#3cf0052be311c34815919fc9a1ade3d981b8fd0b" + integrity sha512-4LsLHdjtIRHdOeiiRMk+Yf04UQjpuQ20paOi1x71z8wnL/47n73DBJ9AEdLQkptlljtsDj+DgLtysl7BeCL4Hg== -cypress@9.7.0: - version "9.7.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.7.0.tgz#bf55b2afd481f7a113ef5604aa8b693564b5e744" - integrity sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q== +cypress@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.0.1.tgz#3a51a38b2f162256c7226e68e902cfe1750e3d92" + integrity sha512-I1Ag5RsPEINfUlQtV6xwkd6ktJuu5QGiKZ3pFa/IXjcyCY6I7CH3gOz0juLOhg/LXOPrQtZH35ulcWDQohyyEA== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -4284,7 +4284,7 @@ cypress@9.7.0: dayjs "^1.10.4" debug "^4.3.2" enquirer "^2.3.6" - eventemitter2 "^6.4.3" + eventemitter2 "6.4.7" execa "4.1.0" executable "^4.1.1" extract-zip "2.0.1" @@ -5292,10 +5292,10 @@ eventemitter-asyncresource@^1.0.0: resolved "https://registry.yarnpkg.com/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz#734ff2e44bf448e627f7748f905d6bdd57bdb65b" integrity sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ== -eventemitter2@^6.4.3: - version "6.4.9" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" - integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== +eventemitter2@6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" + integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== eventemitter3@^4.0.0: version "4.0.7" From f5d18edd0656906c91780a4a227084a0066379c1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 8 Dec 2022 10:42:52 -0600 Subject: [PATCH 02/33] Fix circulary dependency in our Cypress support code. --- cypress/support/commands.ts | 9 ++++++--- cypress/support/e2e.ts | 4 ---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 63d3992298..c3e3058bed 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -4,12 +4,15 @@ // *********************************************** import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; -import { FALLBACK_TEST_REST_BASE_URL } from './e2e'; + +// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL +// from the Angular UI's config.json. See 'login()'. +export const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server'; // Declare Cypress namespace to help with Intellisense & code completion in IDEs // ALL custom commands MUST be listed here for code completion to work -// tslint:disable-next-line:no-namespace declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { /** @@ -107,4 +110,4 @@ Cypress.Commands.add('login', login); cy.get('ds-log-in [data-test="login-button"]').click(); } // Add as a Cypress command (i.e. assign to 'cy.loginViaForm') -Cypress.Commands.add('loginViaForm', loginViaForm); \ No newline at end of file +Cypress.Commands.add('loginViaForm', loginViaForm); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 70da23f044..17950eb629 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -43,10 +43,6 @@ afterEach(() => { // https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data // (This is the data set used in our CI environment) -// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL -// from the Angular UI's config.json. See 'getBaseRESTUrl()' in commands.ts -export const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server'; - // Admin account used for administrative tests export const TEST_ADMIN_USER = Cypress.env('DSPACE_TEST_ADMIN_USER') || 'dspacedemo+admin@gmail.com'; export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace'; From 599e8a777e0824ec9f7c6cc413d5f2ba400a37aa Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 1 Mar 2023 16:49:01 -0600 Subject: [PATCH 03/33] Update to latest cypress, cypress-axe and axe-core --- yarn.lock | 84 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/yarn.lock b/yarn.lock index 306d870ff1..03dcf2f1ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1479,9 +1479,9 @@ integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== "@cypress/request@^2.88.10": - version "2.88.10" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" - integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== + version "2.88.11" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.11.tgz#5a4c7399bc2d7e7ed56e92ce5acb620c8b187047" + integrity sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1496,7 +1496,7 @@ json-stringify-safe "~5.0.1" mime-types "~2.1.19" performance-now "^2.1.0" - qs "~6.5.2" + qs "~6.10.3" safe-buffer "^5.1.2" tough-cookie "~2.5.0" tunnel-agent "^0.6.0" @@ -2287,12 +2287,22 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== -"@types/node@*", "@types/node@>=10.0.0": +"@types/node@*": + version "18.14.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.2.tgz#c076ed1d7b6095078ad3cf21dfeea951842778b1" + integrity sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA== + +"@types/node@>=10.0.0": version "18.11.11" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.11.tgz#1d455ac0211549a8409d3cdb371cd55cc971e8dc" integrity sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g== -"@types/node@^14.14.31", "@types/node@^14.14.9": +"@types/node@^14.14.31": + version "14.18.36" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.36.tgz#c414052cb9d43fab67d679d5f3c641be911f5835" + integrity sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ== + +"@types/node@^14.14.9": version "14.18.34" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.34.tgz#cd2e6fa0dbfb08a62582a7b967558e73c32061ec" integrity sha512-hcU9AIQVHmPnmjRK+XUUYlILlr9pQrsqSrwov/JK1pnf3GTQowVBhx54FbvM0AU/VXGH4i3+vgXS5EguR7fysA== @@ -3144,14 +3154,14 @@ aws-sign2@~0.7.0: integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + version "1.12.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" + integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== axe-core@^4.4.3: - version "4.5.2" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.5.2.tgz#823fdf491ff717ac3c58a52631d4206930c1d9f7" - integrity sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA== + version "4.6.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.3.tgz#fc0db6fdb65cc7a80ccf85286d91d64ababa3ece" + integrity sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg== axios@0.21.4: version "0.21.4" @@ -3692,9 +3702,9 @@ chrome-trace-event@^1.0.2: integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== ci-info@^3.2.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.0.tgz#6d01b3696c59915b6ce057e4aa4adfc2fa25f5ef" - integrity sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog== + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== circular-dependency-plugin@5.2.2: version "5.2.2" @@ -4256,14 +4266,14 @@ custom-event@~1.0.0: integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== cypress-axe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.1.0.tgz#3cf0052be311c34815919fc9a1ade3d981b8fd0b" - integrity sha512-4LsLHdjtIRHdOeiiRMk+Yf04UQjpuQ20paOi1x71z8wnL/47n73DBJ9AEdLQkptlljtsDj+DgLtysl7BeCL4Hg== + version "1.4.0" + resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.4.0.tgz#e67482bfe9e740796bf77c7823f19781a8a2faff" + integrity sha512-Ut7NKfzjyKm0BEbt2WxuKtLkIXmx6FD2j0RwdvO/Ykl7GmB/qRQkwbKLk3VP35+83hiIr8GKD04PDdrTK5BnyA== cypress@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.0.1.tgz#3a51a38b2f162256c7226e68e902cfe1750e3d92" - integrity sha512-I1Ag5RsPEINfUlQtV6xwkd6ktJuu5QGiKZ3pFa/IXjcyCY6I7CH3gOz0juLOhg/LXOPrQtZH35ulcWDQohyyEA== + version "12.7.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.7.0.tgz#69900f82af76cf3ba0ddb9b59ec3b0d38222ab22" + integrity sha512-7rq+nmhzz0u6yabCFyPtADU2OOrYt6pvUau9qV7xyifJ/hnsaw/vkr0tnLlcuuQKUAOC1v1M1e4Z0zG7S0IAvA== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -4282,7 +4292,7 @@ cypress@^12.0.1: commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" - debug "^4.3.2" + debug "^4.3.4" enquirer "^2.3.6" eventemitter2 "6.4.7" execa "4.1.0" @@ -9227,11 +9237,16 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@^2.1.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + q@^1.4.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -9254,6 +9269,13 @@ qs@6.2.3: resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" integrity sha512-AY4g8t3LMboim0t6XWFdz6J5OuJ1ZNYu54SXihS/OMpgyCqYmcAJnWqkNSOjSjWmq3xxy+GF9uWQI2lI/7tKIA== +qs@~6.10.3: + version "6.10.5" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4" + integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ== + dependencies: + side-channel "^1.0.4" + qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" @@ -9878,13 +9900,20 @@ rxjs@^5.5.6: dependencies: symbol-observable "1.0.1" -rxjs@^7.2.0, rxjs@^7.5.1, rxjs@^7.5.5: +rxjs@^7.2.0, rxjs@^7.5.5: version "7.6.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.6.0.tgz#361da5362b6ddaa691a2de0b4f2d32028f1eb5a2" integrity sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ== dependencies: tslib "^2.1.0" +rxjs@^7.5.1: + version "7.8.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" + integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== + dependencies: + tslib "^2.1.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -10945,11 +10974,16 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== +tslib@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" From 1ae3c183d46529172f4912ee308d247ef7ee4dcc Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 2 Mar 2023 14:25:53 -0600 Subject: [PATCH 04/33] Minor test cleanup after upgrade --- cypress/e2e/collection-page.cy.ts | 2 +- cypress/e2e/collection-statistics.cy.ts | 4 ++-- cypress/e2e/community-list.cy.ts | 6 +++--- cypress/e2e/community-page.cy.ts | 2 +- cypress/e2e/community-statistics.cy.ts | 4 ++-- cypress/e2e/item-page.cy.ts | 2 +- cypress/e2e/item-statistics.cy.ts | 6 +++--- cypress/e2e/my-dspace.cy.ts | 6 +++--- cypress/e2e/pagenotfound.cy.ts | 2 +- cypress/e2e/search-page.cy.ts | 4 ++-- cypress/support/utils.ts | 8 ++++++++ .../community-list/community-list.component.html | 3 ++- 12 files changed, 29 insertions(+), 20 deletions(-) diff --git a/cypress/e2e/collection-page.cy.ts b/cypress/e2e/collection-page.cy.ts index be7ef8d182..630fa0a18b 100644 --- a/cypress/e2e/collection-page.cy.ts +++ b/cypress/e2e/collection-page.cy.ts @@ -7,7 +7,7 @@ describe('Collection Page', () => { cy.visit('/collections/' + TEST_COLLECTION); // tag must be loaded - cy.get('ds-collection-page').should('exist'); + cy.get('ds-collection-page').should('be.visible'); // Analyze for accessibility issues testA11y('ds-collection-page'); diff --git a/cypress/e2e/collection-statistics.cy.ts b/cypress/e2e/collection-statistics.cy.ts index 58601f1c4e..f3e36e7bc5 100644 --- a/cypress/e2e/collection-statistics.cy.ts +++ b/cypress/e2e/collection-statistics.cy.ts @@ -12,7 +12,7 @@ describe('Collection Statistics Page', () => { it('should contain a "Total visits" section', () => { cy.visit(COLLECTIONSTATISTICSPAGE); - cy.get('.' + TEST_COLLECTION + '_TotalVisits').should('exist'); + cy.get('.' + TEST_COLLECTION + '_TotalVisits').should('be.visible'); }); it('should contain a "Total visits per month" section', () => { @@ -24,7 +24,7 @@ describe('Collection Statistics Page', () => { cy.visit(COLLECTIONSTATISTICSPAGE); // tag must be loaded - cy.get('ds-collection-statistics-page').should('exist'); + cy.get('ds-collection-statistics-page').should('be.visible'); // Analyze for accessibility issues testA11y('ds-collection-statistics-page'); diff --git a/cypress/e2e/community-list.cy.ts b/cypress/e2e/community-list.cy.ts index a7ba72b74a..7b60b59dbc 100644 --- a/cypress/e2e/community-list.cy.ts +++ b/cypress/e2e/community-list.cy.ts @@ -7,10 +7,10 @@ describe('Community List Page', () => { cy.visit('/community-list'); // tag must be loaded - cy.get('ds-community-list-page').should('exist'); + cy.get('ds-community-list-page').should('be.visible'); - // Open first Community (to show Collections)...that way we scan sub-elements as well - cy.get('ds-community-list :nth-child(1) > .btn-group > .btn').click(); + // Open every expand button on page, so that we can scan sub-elements as well + cy.get('[data-test="expand-button"]').click({ multiple: true }); // Analyze for accessibility issues // Disable heading-order checks until it is fixed diff --git a/cypress/e2e/community-page.cy.ts b/cypress/e2e/community-page.cy.ts index 080dccf820..34f03e829a 100644 --- a/cypress/e2e/community-page.cy.ts +++ b/cypress/e2e/community-page.cy.ts @@ -7,7 +7,7 @@ describe('Community Page', () => { cy.visit('/communities/' + TEST_COMMUNITY); // tag must be loaded - cy.get('ds-community-page').should('exist'); + cy.get('ds-community-page').should('be.visible'); // Analyze for accessibility issues testA11y('ds-community-page',); diff --git a/cypress/e2e/community-statistics.cy.ts b/cypress/e2e/community-statistics.cy.ts index 4eb1d7935d..994b73dad9 100644 --- a/cypress/e2e/community-statistics.cy.ts +++ b/cypress/e2e/community-statistics.cy.ts @@ -12,7 +12,7 @@ describe('Community Statistics Page', () => { it('should contain a "Total visits" section', () => { cy.visit(COMMUNITYSTATISTICSPAGE); - cy.get('.' + TEST_COMMUNITY + '_TotalVisits').should('exist'); + cy.get('.' + TEST_COMMUNITY + '_TotalVisits').should('be.visible'); }); it('should contain a "Total visits per month" section', () => { @@ -24,7 +24,7 @@ describe('Community Statistics Page', () => { cy.visit(COMMUNITYSTATISTICSPAGE); // tag must be loaded - cy.get('ds-community-statistics-page').should('exist'); + cy.get('ds-community-statistics-page').should('be.visible'); // Analyze for accessibility issues testA11y('ds-community-statistics-page'); diff --git a/cypress/e2e/item-page.cy.ts b/cypress/e2e/item-page.cy.ts index f55cfc4b4d..7f6282a36f 100644 --- a/cypress/e2e/item-page.cy.ts +++ b/cypress/e2e/item-page.cy.ts @@ -16,7 +16,7 @@ describe('Item Page', () => { cy.visit(ENTITYPAGE); // tag must be loaded - cy.get('ds-item-page').should('exist'); + cy.get('ds-item-page').should('be.visible'); // Analyze for accessibility issues // Disable heading-order checks until it is fixed diff --git a/cypress/e2e/item-statistics.cy.ts b/cypress/e2e/item-statistics.cy.ts index 4fa90d4ef8..50e8f193da 100644 --- a/cypress/e2e/item-statistics.cy.ts +++ b/cypress/e2e/item-statistics.cy.ts @@ -12,13 +12,13 @@ describe('Item Statistics Page', () => { it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => { cy.visit(ITEMSTATISTICSPAGE); - cy.get('ds-item-statistics-page').should('exist'); + cy.get('ds-item-statistics-page').should('be.visible'); cy.get('ds-item-page').should('not.exist'); }); it('should contain a "Total visits" section', () => { cy.visit(ITEMSTATISTICSPAGE); - cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisits').should('exist'); + cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisits').should('be.visible'); }); it('should contain a "Total visits per month" section', () => { @@ -30,7 +30,7 @@ describe('Item Statistics Page', () => { cy.visit(ITEMSTATISTICSPAGE); // tag must be loaded - cy.get('ds-item-statistics-page').should('exist'); + cy.get('ds-item-statistics-page').should('be.visible'); // Analyze for accessibility issues testA11y('ds-item-statistics-page'); diff --git a/cypress/e2e/my-dspace.cy.ts b/cypress/e2e/my-dspace.cy.ts index d6fa707f82..cf717d936d 100644 --- a/cypress/e2e/my-dspace.cy.ts +++ b/cypress/e2e/my-dspace.cy.ts @@ -9,7 +9,7 @@ describe('My DSpace page', () => { // This page is restricted, so we will be shown the login form. Fill it out & submit. cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); - cy.get('ds-my-dspace-page').should('exist'); + cy.get('ds-my-dspace-page').should('be.visible'); // At least one recent submission should be displayed cy.get('[data-test="list-object"]').should('be.visible'); @@ -42,12 +42,12 @@ describe('My DSpace page', () => { // This page is restricted, so we will be shown the login form. Fill it out & submit. cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); - cy.get('ds-my-dspace-page').should('exist'); + cy.get('ds-my-dspace-page').should('be.visible'); // Click button in sidebar to display detailed view cy.get('ds-search-sidebar [data-test="detail-view"]').click(); - cy.get('ds-object-detail').should('exist'); + cy.get('ds-object-detail').should('be.visible'); // Analyze for accessibility issues testA11y('ds-my-dspace-page', diff --git a/cypress/e2e/pagenotfound.cy.ts b/cypress/e2e/pagenotfound.cy.ts index 48520bcaa3..43e3c3af24 100644 --- a/cypress/e2e/pagenotfound.cy.ts +++ b/cypress/e2e/pagenotfound.cy.ts @@ -2,7 +2,7 @@ describe('PageNotFound', () => { it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => { // request an invalid page (UUIDs at root path aren't valid) cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false }); - cy.get('ds-pagenotfound').should('exist'); + cy.get('ds-pagenotfound').should('be.visible'); }); it('should not contain element ds-pagenotfound when navigating to existing page', () => { diff --git a/cypress/e2e/search-page.cy.ts b/cypress/e2e/search-page.cy.ts index 8078c4a969..5c1d068b74 100644 --- a/cypress/e2e/search-page.cy.ts +++ b/cypress/e2e/search-page.cy.ts @@ -17,7 +17,7 @@ describe('Search Page', () => { cy.get('[data-test="search-box"]').should('have.value', TEST_SEARCH_TERM); // tag must be loaded - cy.get('ds-search-page').should('exist'); + cy.get('ds-search-page').should('be.visible'); // At least one search result should be displayed cy.get('[data-test="list-object"]').should('be.visible'); @@ -51,7 +51,7 @@ describe('Search Page', () => { cy.get('ds-search-sidebar [data-test="grid-view"]').click(); // tag must be loaded - cy.get('ds-search-page').should('exist'); + cy.get('ds-search-page').should('be.visible'); // At least one grid object (card) should be displayed cy.get('[data-test="grid-object"]').should('be.visible'); diff --git a/cypress/support/utils.ts b/cypress/support/utils.ts index 96575969e8..fc0820d25c 100644 --- a/cypress/support/utils.ts +++ b/cypress/support/utils.ts @@ -40,5 +40,13 @@ export const testA11y = (context?: any, options?: Options) => { { id: 'color-contrast', enabled: false }, ] }); + // Default retries to 2, meaning this accessibility test will retry up to 2 times. + const a11yRetries = 2; + if (options) { + options.retries = a11yRetries; + } else { + options = { retries: a11yRetries } as Options; + } + cy.checkA11y(context, options, terminalLog); }; diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 821cb58473..ea772bb891 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -28,7 +28,8 @@ [title]="'toggle ' + node.name" [attr.aria-label]="'toggle ' + node.name" (click)="toggleExpanded(node)" - [ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'"> + [ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'" + [attr.data-test]="(hasChild(null, node)| async) ? 'expand-button' : ''"> From 934c23a5503fdffa24bd931914beb61112c9654a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 2 Mar 2023 15:47:56 -0600 Subject: [PATCH 05/33] Fix accessibility issue where restricted objects have no name in statistics table --- .../statistics-table/statistics-table.component.ts | 9 ++++++--- src/assets/i18n/en.json5 | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/statistics-page/statistics-table/statistics-table.component.ts b/src/app/statistics-page/statistics-table/statistics-table.component.ts index 5916adc023..45924caa8d 100644 --- a/src/app/statistics-page/statistics-table/statistics-table.component.ts +++ b/src/app/statistics-page/statistics-table/statistics-table.component.ts @@ -3,8 +3,10 @@ import { Point, UsageReport } from '../../core/statistics/models/usage-report.mo import { Observable, of } from 'rxjs'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { map } from 'rxjs/operators'; -import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../core/shared/operators'; +import { getRemoteDataPayload, getFinishedRemoteData } from '../../core/shared/operators'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; +import { TranslateService } from '@ngx-translate/core'; +import { isEmpty } from '../../shared/empty.util'; /** * Component representing a statistics table for a given usage report. @@ -35,6 +37,7 @@ export class StatisticsTableComponent implements OnInit { constructor( protected dsoService: DSpaceObjectDataService, protected nameService: DSONameService, + private translateService: TranslateService, ) { } @@ -54,9 +57,9 @@ export class StatisticsTableComponent implements OnInit { switch (this.report.reportType) { case 'TotalVisits': return this.dsoService.findById(point.id).pipe( - getFirstSucceededRemoteData(), + getFinishedRemoteData(), getRemoteDataPayload(), - map((item) => this.nameService.getName(item)), + map((item) => !isEmpty(item) ? this.nameService.getName(item) : this.translateService.instant('statistics.table.no-name')), ); case 'TopCities': case 'topCountries': diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a8fc1b1177..9ff3aa1934 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4031,6 +4031,8 @@ "statistics.table.header.views": "Views", + "statistics.table.no-name": "(object name could not be loaded)", + "submission.edit.breadcrumbs": "Edit Submission", From aa7c644e6aa23e83d10d3ca71588334b6e79f293 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 2 Mar 2023 16:06:12 -0600 Subject: [PATCH 06/33] Minor configuration cleanup for Cypress --- .github/workflows/build.yml | 4 ++++ cypress.config.ts | 23 ++++++++++++++++++----- cypress/support/e2e.ts | 4 ++-- cypress/support/utils.ts | 8 -------- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3b7aff689..3fe88a58b9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,12 +15,16 @@ jobs: env: # The ci step will test the dspace-angular code against DSpace REST. # Direct that step to utilize a DSpace REST service that has been started in docker. + # NOTE: These settings should be kept in sync with those in [src]/docker/docker-compose-ci.yml DSPACE_REST_HOST: 127.0.0.1 DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: '/server' DSPACE_REST_SSL: false # Spin up UI on 127.0.0.1 to avoid host resolution issues in e2e tests with Node 18+ DSPACE_UI_HOST: 127.0.0.1 + DSPACE_UI_PORT: 4000 + # Tell Cypress to run e2e tests using the same UI URL + CYPRESS_BASE_URL: http://127.0.0.1:4000 # When Chrome version is specified, we pin to a specific version of Chrome # Comment this out to use the latest release #CHROME_VERSION: "90.0.4430.212-1" diff --git a/cypress.config.ts b/cypress.config.ts index e45b8a5396..91eeb9838b 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'cypress' +import { defineConfig } from 'cypress'; export default defineConfig({ videosFolder: 'cypress/videos', @@ -9,23 +9,36 @@ export default defineConfig({ openMode: 0, }, env: { + // Global constants used in DSpace e2e tests (see also ./cypress/support/e2e.ts) + // May be overridden in our cypress.json config file using specified environment variables. + // Default values listed here are all valid for the Demo Entities Data set available at + // https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data + // (This is the data set used in our CI environment) + + // Admin account used for administrative tests DSPACE_TEST_ADMIN_USER: 'dspacedemo+admin@gmail.com', DSPACE_TEST_ADMIN_PASSWORD: 'dspace', + // Community/collection/publication used for view/edit tests DSPACE_TEST_COMMUNITY: '0958c910-2037-42a9-81c7-dca80e3892b4', DSPACE_TEST_COLLECTION: '282164f5-d325-4740-8dd1-fa4d6d3e7200', DSPACE_TEST_ENTITY_PUBLICATION: 'e98b0f27-5c19-49a0-960d-eb6ad5287067', + // Search term (should return results) used in search tests DSPACE_TEST_SEARCH_TERM: 'test', + // Collection used for submission tests DSPACE_TEST_SUBMIT_COLLECTION_NAME: 'Sample Collection', DSPACE_TEST_SUBMIT_COLLECTION_UUID: '9d8334e9-25d3-4a67-9cea-3dffdef80144', + // Account used to test basic submission process DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com', DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace', }, e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. + // Setup our plugins for e2e tests setupNodeEvents(on, config) { - return require('./cypress/plugins/index.ts')(on, config) + return require('./cypress/plugins/index.ts')(on, config); }, + // This is the base URL that Cypress will run all tests against + // It can be overridden via the CYPRESS_BASE_URL environment variable + // (By default we set this to a value which should work in most development environments) baseUrl: 'http://localhost:4000', }, -}) +}); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 17950eb629..9f2edec31b 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -30,11 +30,11 @@ beforeEach(() => { // For better stability between tests, we visit "about:blank" (i.e. blank page) after each test. // This ensures any remaining/outstanding XHR requests are killed, so they don't affect the next test. // Borrowed from: https://glebbahmutov.com/blog/visit-blank-page-between-tests/ -afterEach(() => { +/*afterEach(() => { cy.window().then((win) => { win.location.href = 'about:blank'; }); -}); +});*/ // Global constants used in tests diff --git a/cypress/support/utils.ts b/cypress/support/utils.ts index fc0820d25c..96575969e8 100644 --- a/cypress/support/utils.ts +++ b/cypress/support/utils.ts @@ -40,13 +40,5 @@ export const testA11y = (context?: any, options?: Options) => { { id: 'color-contrast', enabled: false }, ] }); - // Default retries to 2, meaning this accessibility test will retry up to 2 times. - const a11yRetries = 2; - if (options) { - options.retries = a11yRetries; - } else { - options = { retries: a11yRetries } as Options; - } - cy.checkA11y(context, options, terminalLog); }; From d38ba9cf0ecc0c89ee0630ef133d71243374f583 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 3 Mar 2023 10:04:23 -0600 Subject: [PATCH 07/33] Make statistics tests more stable by waiting on page to fully load --- cypress/e2e/collection-statistics.cy.ts | 15 ++++++++++----- cypress/e2e/community-statistics.cy.ts | 15 ++++++++++----- cypress/e2e/homepage-statistics.cy.ts | 11 +++++++++-- cypress/e2e/item-statistics.cy.ts | 15 ++++++++++----- cypress/support/e2e.ts | 7 +++++++ .../statistics-table.component.html | 4 ++-- 6 files changed, 48 insertions(+), 19 deletions(-) diff --git a/cypress/e2e/collection-statistics.cy.ts b/cypress/e2e/collection-statistics.cy.ts index f3e36e7bc5..6df4e9a454 100644 --- a/cypress/e2e/collection-statistics.cy.ts +++ b/cypress/e2e/collection-statistics.cy.ts @@ -1,23 +1,24 @@ -import { TEST_COLLECTION } from 'cypress/support/e2e'; +import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_COLLECTION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Collection Statistics Page', () => { - const COLLECTIONSTATISTICSPAGE = '/statistics/collections/' + TEST_COLLECTION; + const COLLECTIONSTATISTICSPAGE = '/statistics/collections/'.concat(TEST_COLLECTION); it('should load if you click on "Statistics" from a Collection page', () => { - cy.visit('/collections/' + TEST_COLLECTION); + cy.visit('/collections/'.concat(TEST_COLLECTION)); cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click(); cy.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE); }); it('should contain a "Total visits" section', () => { cy.visit(COLLECTIONSTATISTICSPAGE); - cy.get('.' + TEST_COLLECTION + '_TotalVisits').should('be.visible'); + cy.get('table[data-test="TotalVisits"]').should('be.visible'); }); it('should contain a "Total visits per month" section', () => { cy.visit(COLLECTIONSTATISTICSPAGE); - cy.get('.' + TEST_COLLECTION + '_TotalVisitsPerMonth').should('exist'); + // Check just for existence because this table is empty in CI environment as it's historical data + cy.get('.'.concat(TEST_COLLECTION).concat('_TotalVisitsPerMonth')).should('exist'); }); it('should pass accessibility tests', () => { @@ -26,6 +27,10 @@ describe('Collection Statistics Page', () => { // tag must be loaded cy.get('ds-collection-statistics-page').should('be.visible'); + // Verify / wait until "Total Visits" table's label is non-empty + // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) + cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); + // Analyze for accessibility issues testA11y('ds-collection-statistics-page'); }); diff --git a/cypress/e2e/community-statistics.cy.ts b/cypress/e2e/community-statistics.cy.ts index 994b73dad9..710450e797 100644 --- a/cypress/e2e/community-statistics.cy.ts +++ b/cypress/e2e/community-statistics.cy.ts @@ -1,23 +1,24 @@ -import { TEST_COMMUNITY } from 'cypress/support/e2e'; +import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_COMMUNITY } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Community Statistics Page', () => { - const COMMUNITYSTATISTICSPAGE = '/statistics/communities/' + TEST_COMMUNITY; + const COMMUNITYSTATISTICSPAGE = '/statistics/communities/'.concat(TEST_COMMUNITY); it('should load if you click on "Statistics" from a Community page', () => { - cy.visit('/communities/' + TEST_COMMUNITY); + cy.visit('/communities/'.concat(TEST_COMMUNITY)); cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click(); cy.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE); }); it('should contain a "Total visits" section', () => { cy.visit(COMMUNITYSTATISTICSPAGE); - cy.get('.' + TEST_COMMUNITY + '_TotalVisits').should('be.visible'); + cy.get('table[data-test="TotalVisits"]').should('be.visible'); }); it('should contain a "Total visits per month" section', () => { cy.visit(COMMUNITYSTATISTICSPAGE); - cy.get('.' + TEST_COMMUNITY + '_TotalVisitsPerMonth').should('exist'); + // Check just for existence because this table is empty in CI environment as it's historical data + cy.get('.'.concat(TEST_COMMUNITY).concat('_TotalVisitsPerMonth')).should('exist'); }); it('should pass accessibility tests', () => { @@ -26,6 +27,10 @@ describe('Community Statistics Page', () => { // tag must be loaded cy.get('ds-community-statistics-page').should('be.visible'); + // Verify / wait until "Total Visits" table's label is non-empty + // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) + cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); + // Analyze for accessibility issues testA11y('ds-community-statistics-page'); }); diff --git a/cypress/e2e/homepage-statistics.cy.ts b/cypress/e2e/homepage-statistics.cy.ts index fe0311f87e..86b93d4259 100644 --- a/cypress/e2e/homepage-statistics.cy.ts +++ b/cypress/e2e/homepage-statistics.cy.ts @@ -1,3 +1,4 @@ +import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Site Statistics Page', () => { @@ -10,8 +11,14 @@ describe('Site Statistics Page', () => { it('should pass accessibility tests', () => { cy.visit('/statistics'); - // tag must be loaded - cy.get('ds-site-statistics-page').should('exist'); + // tag must be visable + cy.get('ds-site-statistics-page').should('be.visible'); + + // Verify / wait until "Total Visits" table's *last* label is non-empty + // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) + cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').last().contains(REGEX_MATCH_NON_EMPTY_TEXT); + // Wait an extra 500ms, just so all entries in Total Visits have loaded. + cy.wait(500); // Analyze for accessibility issues testA11y('ds-site-statistics-page'); diff --git a/cypress/e2e/item-statistics.cy.ts b/cypress/e2e/item-statistics.cy.ts index 50e8f193da..9b90cb24af 100644 --- a/cypress/e2e/item-statistics.cy.ts +++ b/cypress/e2e/item-statistics.cy.ts @@ -1,11 +1,11 @@ -import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; +import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Item Statistics Page', () => { - const ITEMSTATISTICSPAGE = '/statistics/items/' + TEST_ENTITY_PUBLICATION; + const ITEMSTATISTICSPAGE = '/statistics/items/'.concat(TEST_ENTITY_PUBLICATION); it('should load if you click on "Statistics" from an Item/Entity page', () => { - cy.visit('/entities/publication/' + TEST_ENTITY_PUBLICATION); + cy.visit('/entities/publication/'.concat(TEST_ENTITY_PUBLICATION)); cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click(); cy.location('pathname').should('eq', ITEMSTATISTICSPAGE); }); @@ -18,12 +18,13 @@ describe('Item Statistics Page', () => { it('should contain a "Total visits" section', () => { cy.visit(ITEMSTATISTICSPAGE); - cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisits').should('be.visible'); + cy.get('table[data-test="TotalVisits"]').should('be.visible'); }); it('should contain a "Total visits per month" section', () => { cy.visit(ITEMSTATISTICSPAGE); - cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisitsPerMonth').should('exist'); + // Check just for existence because this table is empty in CI environment as it's historical data + cy.get('.'.concat(TEST_ENTITY_PUBLICATION).concat('_TotalVisitsPerMonth')).should('exist'); }); it('should pass accessibility tests', () => { @@ -32,6 +33,10 @@ describe('Item Statistics Page', () => { // tag must be loaded cy.get('ds-item-statistics-page').should('be.visible'); + // Verify / wait until "Total Visits" table's label is non-empty + // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) + cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); + // Analyze for accessibility issues testA11y('ds-item-statistics-page'); }); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 9f2edec31b..dd7ee1824c 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -57,3 +57,10 @@ export const TEST_SUBMIT_COLLECTION_NAME = Cypress.env('DSPACE_TEST_SUBMIT_COLLE export const TEST_SUBMIT_COLLECTION_UUID = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID') || '9d8334e9-25d3-4a67-9cea-3dffdef80144'; export const TEST_SUBMIT_USER = Cypress.env('DSPACE_TEST_SUBMIT_USER') || 'dspacedemo+submit@gmail.com'; export const TEST_SUBMIT_USER_PASSWORD = Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD') || 'dspace'; + + +// USEFUL REGEX for testing + +// Match any string that contains at least one non-space character +// Can be used with "contains()" to determine if an element has a non-empty text value +export const REGEX_MATCH_NON_EMPTY_TEXT = /^(?!\s*$).+/; diff --git a/src/app/statistics-page/statistics-table/statistics-table.component.html b/src/app/statistics-page/statistics-table/statistics-table.component.html index fb042b25c3..5a333b9658 100644 --- a/src/app/statistics-page/statistics-table/statistics-table.component.html +++ b/src/app/statistics-page/statistics-table/statistics-table.component.html @@ -5,7 +5,7 @@ {{ 'statistics.table.title.' + report.reportType | translate }} - +
@@ -20,7 +20,7 @@ -
+ {{ getLabel(point) | async }} Date: Fri, 3 Mar 2023 10:08:50 -0600 Subject: [PATCH 08/33] Fix lint warnings by switching + to concat() --- cypress/e2e/breadcrumbs.cy.ts | 2 +- cypress/e2e/collection-page.cy.ts | 2 +- cypress/e2e/community-page.cy.ts | 2 +- cypress/e2e/item-page.cy.ts | 4 ++-- cypress/e2e/login-modal.cy.ts | 2 +- cypress/e2e/my-dspace.cy.ts | 2 +- cypress/e2e/search-navbar.cy.ts | 6 +++--- cypress/e2e/search-page.cy.ts | 4 ++-- cypress/e2e/submission.cy.ts | 6 +++--- cypress/support/commands.ts | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cypress/e2e/breadcrumbs.cy.ts b/cypress/e2e/breadcrumbs.cy.ts index 157cfdce20..ea6acdafcd 100644 --- a/cypress/e2e/breadcrumbs.cy.ts +++ b/cypress/e2e/breadcrumbs.cy.ts @@ -4,7 +4,7 @@ import { testA11y } from 'cypress/support/utils'; describe('Breadcrumbs', () => { it('should pass accessibility tests', () => { // Visit an Item, as those have more breadcrumbs - cy.visit('/entities/publication/' + TEST_ENTITY_PUBLICATION); + cy.visit('/entities/publication/'.concat(TEST_ENTITY_PUBLICATION)); // Wait for breadcrumbs to be visible cy.get('ds-breadcrumbs').should('be.visible'); diff --git a/cypress/e2e/collection-page.cy.ts b/cypress/e2e/collection-page.cy.ts index 630fa0a18b..a034b4361d 100644 --- a/cypress/e2e/collection-page.cy.ts +++ b/cypress/e2e/collection-page.cy.ts @@ -4,7 +4,7 @@ import { testA11y } from 'cypress/support/utils'; describe('Collection Page', () => { it('should pass accessibility tests', () => { - cy.visit('/collections/' + TEST_COLLECTION); + cy.visit('/collections/'.concat(TEST_COLLECTION)); // tag must be loaded cy.get('ds-collection-page').should('be.visible'); diff --git a/cypress/e2e/community-page.cy.ts b/cypress/e2e/community-page.cy.ts index 34f03e829a..6c628e21ce 100644 --- a/cypress/e2e/community-page.cy.ts +++ b/cypress/e2e/community-page.cy.ts @@ -4,7 +4,7 @@ import { testA11y } from 'cypress/support/utils'; describe('Community Page', () => { it('should pass accessibility tests', () => { - cy.visit('/communities/' + TEST_COMMUNITY); + cy.visit('/communities/'.concat(TEST_COMMUNITY)); // tag must be loaded cy.get('ds-community-page').should('be.visible'); diff --git a/cypress/e2e/item-page.cy.ts b/cypress/e2e/item-page.cy.ts index 7f6282a36f..9eed711776 100644 --- a/cypress/e2e/item-page.cy.ts +++ b/cypress/e2e/item-page.cy.ts @@ -3,8 +3,8 @@ import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Item Page', () => { - const ITEMPAGE = '/items/' + TEST_ENTITY_PUBLICATION; - const ENTITYPAGE = '/entities/publication/' + TEST_ENTITY_PUBLICATION; + const ITEMPAGE = '/items/'.concat(TEST_ENTITY_PUBLICATION); + const ENTITYPAGE = '/entities/publication/'.concat(TEST_ENTITY_PUBLICATION); // Test that entities will redirect to /entities/[type]/[uuid] when accessed via /items/[uuid] it('should redirect to the entity page when navigating to an item page', () => { diff --git a/cypress/e2e/login-modal.cy.ts b/cypress/e2e/login-modal.cy.ts index 431237c9fb..b169634cfa 100644 --- a/cypress/e2e/login-modal.cy.ts +++ b/cypress/e2e/login-modal.cy.ts @@ -36,7 +36,7 @@ const page = { describe('Login Modal', () => { it('should login when clicking button & stay on same page', () => { - const ENTITYPAGE = '/entities/publication/' + TEST_ENTITY_PUBLICATION; + const ENTITYPAGE = '/entities/publication/'.concat(TEST_ENTITY_PUBLICATION); cy.visit(ENTITYPAGE); // Login menu should exist diff --git a/cypress/e2e/my-dspace.cy.ts b/cypress/e2e/my-dspace.cy.ts index cf717d936d..79786c298a 100644 --- a/cypress/e2e/my-dspace.cy.ts +++ b/cypress/e2e/my-dspace.cy.ts @@ -80,7 +80,7 @@ describe('My DSpace page', () => { cy.get('ds-authorized-collection-selector input[type="search"]').type(TEST_SUBMIT_COLLECTION_NAME); // Click on the button matching that known Collection name - cy.get('ds-authorized-collection-selector button[title="' + TEST_SUBMIT_COLLECTION_NAME + '"]').click(); + cy.get('ds-authorized-collection-selector button[title="'.concat(TEST_SUBMIT_COLLECTION_NAME).concat('"]')).click(); // New URL should include /workspaceitems, as we've started a new submission cy.url().should('include', '/workspaceitems'); diff --git a/cypress/e2e/search-navbar.cy.ts b/cypress/e2e/search-navbar.cy.ts index b13f585323..648db17fe6 100644 --- a/cypress/e2e/search-navbar.cy.ts +++ b/cypress/e2e/search-navbar.cy.ts @@ -27,7 +27,7 @@ describe('Search from Navigation Bar', () => { page.fillOutQueryInNavBar(query); page.submitQueryByPressingEnter(); // New URL should include query param - cy.url().should('include', 'query=' + query); + cy.url().should('include', 'query='.concat(query)); // Wait for search results to come back from the above GET command cy.wait('@search-results'); // At least one search result should be displayed @@ -42,7 +42,7 @@ describe('Search from Navigation Bar', () => { page.fillOutQueryInNavBar(query); page.submitQueryByPressingEnter(); // New URL should include query param - cy.url().should('include', 'query=' + query); + cy.url().should('include', 'query='.concat(query)); // Wait for search results to come back from the above GET command cy.wait('@search-results'); // At least one search result should be displayed @@ -57,7 +57,7 @@ describe('Search from Navigation Bar', () => { page.fillOutQueryInNavBar(query); page.submitQueryByPressingIcon(); // New URL should include query param - cy.url().should('include', 'query=' + query); + cy.url().should('include', 'query='.concat(query)); // Wait for search results to come back from the above GET command cy.wait('@search-results'); // At least one search result should be displayed diff --git a/cypress/e2e/search-page.cy.ts b/cypress/e2e/search-page.cy.ts index 5c1d068b74..24519cc236 100644 --- a/cypress/e2e/search-page.cy.ts +++ b/cypress/e2e/search-page.cy.ts @@ -13,7 +13,7 @@ describe('Search Page', () => { }); it('should load results and pass accessibility tests', () => { - cy.visit('/search?query=' + TEST_SEARCH_TERM); + cy.visit('/search?query='.concat(TEST_SEARCH_TERM)); cy.get('[data-test="search-box"]').should('have.value', TEST_SEARCH_TERM); // tag must be loaded @@ -45,7 +45,7 @@ describe('Search Page', () => { }); it('should have a working grid view that passes accessibility tests', () => { - cy.visit('/search?query=' + TEST_SEARCH_TERM); + cy.visit('/search?query='.concat(TEST_SEARCH_TERM)); // Click button in sidebar to display grid view cy.get('ds-search-sidebar [data-test="grid-view"]').click(); diff --git a/cypress/e2e/submission.cy.ts b/cypress/e2e/submission.cy.ts index 010513dea5..8262bc841e 100644 --- a/cypress/e2e/submission.cy.ts +++ b/cypress/e2e/submission.cy.ts @@ -5,7 +5,7 @@ describe('New Submission page', () => { it('should create a new submission when using /submit path & pass accessibility', () => { // Test that calling /submit with collection & entityType will create a new submission - cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); + cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none')); // This page is restricted, so we will be shown the login form. Fill it out & submit. cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); @@ -33,7 +33,7 @@ describe('New Submission page', () => { it('should block submission & show errors if required fields are missing', () => { // Create a new submission - cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); + cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none')); // This page is restricted, so we will be shown the login form. Fill it out & submit. cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); @@ -93,7 +93,7 @@ describe('New Submission page', () => { it('should allow for deposit if all required fields completed & file uploaded', () => { // Create a new submission - cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); + cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none')); // This page is restricted, so we will be shown the login form. Fill it out & submit. cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index c3e3058bed..75d235f056 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -56,7 +56,7 @@ function login(email: string, password: string): void { if (!config.rest.baseUrl) { console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL); } else { - console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: " + config.rest.baseUrl); + console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: ".concat(config.rest.baseUrl)); baseRestUrl = config.rest.baseUrl; } From 79cb51b2c153f4a8c1a44f6b6b825fb7849973e0 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 3 Mar 2023 21:45:37 +0100 Subject: [PATCH 09/33] Fix hints not being displayed for dynamic array fields --- .../ds-dynamic-form-control-container.component.html | 2 +- .../ds-dynamic-form-control-container.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index 55e354ea7a..ad2827772f 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -15,7 +15,7 @@ -
0) { const listGroup = this.group.controls[this.model.id] as FormGroup; + if (this.model.repeatable && this.model.required) { + listGroup.setValidators(this.hasAtLeastOneVocabularyEntry()); + } const pageInfo: PageInfo = new PageInfo({ elementsPerPage: 9999, currentPage: 1 } as PageInfo); @@ -121,7 +123,7 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen let tempList: ListItem[] = []; this.optionsList = entries.page; // Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength' - entries.page.forEach((option, key) => { + entries.page.forEach((option: VocabularyEntry, key: number) => { const value = option.authority || option.value; const checked: boolean = isNotEmpty(findKey( this.model.value, @@ -156,4 +158,13 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen } } + /** + * Checks if at least one {@link VocabularyEntry} has been selected. + */ + hasAtLeastOneVocabularyEntry(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + return control && control.value && Object.values(control.value).find((checked: boolean) => checked === true) ? null : this.model.errorMessages; + }; + } + } diff --git a/src/app/shared/form/builder/form-builder.service.spec.ts b/src/app/shared/form/builder/form-builder.service.spec.ts index cea4d7df6e..bb476e010a 100644 --- a/src/app/shared/form/builder/form-builder.service.spec.ts +++ b/src/app/shared/form/builder/form-builder.service.spec.ts @@ -220,10 +220,16 @@ describe('FormBuilderService test suite', () => { new DynamicListCheckboxGroupModel({ id: 'testCheckboxList', vocabularyOptions: vocabularyOptions, - repeatable: true + repeatable: true, + required: false, }), - new DynamicListRadioGroupModel({ id: 'testRadioList', vocabularyOptions: vocabularyOptions, repeatable: false }), + new DynamicListRadioGroupModel({ + id: 'testRadioList', + vocabularyOptions: vocabularyOptions, + repeatable: false, + required: false, + }), new DynamicRelationGroupModel({ submissionId, From 371bd072b31021b1c744639c7bca0703502a9690 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 6 Mar 2023 16:13:43 -0600 Subject: [PATCH 11/33] Add generateViewEvent() util to generate stats in e2e tests. Refactored xsrf.interceptor.ts to move constants to a separate file so they can be reused in e2e tests easily. --- cypress/e2e/homepage-statistics.cy.ts | 7 +- cypress/support/commands.ts | 129 ++++++++++++++---- .../auth/server-auth-request.service.spec.ts | 2 +- .../core/auth/server-auth-request.service.ts | 2 +- src/app/core/xsrf/xsrf.constants.ts | 33 +++++ src/app/core/xsrf/xsrf.interceptor.ts | 10 +- .../upload/uploader/uploader.component.ts | 2 +- 7 files changed, 144 insertions(+), 41 deletions(-) create mode 100644 src/app/core/xsrf/xsrf.constants.ts diff --git a/cypress/e2e/homepage-statistics.cy.ts b/cypress/e2e/homepage-statistics.cy.ts index 86b93d4259..2a1ab9785a 100644 --- a/cypress/e2e/homepage-statistics.cy.ts +++ b/cypress/e2e/homepage-statistics.cy.ts @@ -1,5 +1,6 @@ -import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e'; +import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; +import '../support/commands'; describe('Site Statistics Page', () => { it('should load if you click on "Statistics" from homepage', () => { @@ -9,6 +10,10 @@ describe('Site Statistics Page', () => { }); it('should pass accessibility tests', () => { + // generate 2 view events on an Item's page + cy.generateViewEvent(TEST_ENTITY_PUBLICATION, 'item'); + cy.generateViewEvent(TEST_ENTITY_PUBLICATION, 'item'); + cy.visit('/statistics'); // tag must be visable diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 75d235f056..2d3947d07f 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -4,10 +4,12 @@ // *********************************************** import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; +import { DSPACE_XSRF_COOKIE, XSRF_REQUEST_HEADER } from 'src/app/core/xsrf/xsrf.constants'; // NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL // from the Angular UI's config.json. See 'login()'. export const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server'; +export const FALLBACK_TEST_REST_DOMAIN = 'localhost'; // Declare Cypress namespace to help with Intellisense & code completion in IDEs // ALL custom commands MUST be listed here for code completion to work @@ -30,6 +32,15 @@ declare global { * @param password password to login as */ loginViaForm(email: string, password: string): typeof loginViaForm; + + /** + * Generate view event for given object. Useful for testing statistics pages with + * pre-generated statistics. This just generates a single "hit", but can be called multiple times to + * generate multiple hits. + * @param uuid UUID of object + * @param dsoType type of DSpace Object (e.g. "item", "collection", "community") + */ + generateViewEvent(uuid: string, dsoType: string): typeof generateViewEvent; } } } @@ -56,52 +67,57 @@ function login(email: string, password: string): void { if (!config.rest.baseUrl) { console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL); } else { - console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: ".concat(config.rest.baseUrl)); + //console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: ".concat(config.rest.baseUrl)); baseRestUrl = config.rest.baseUrl; } - // To login via REST, first we have to do a GET to obtain a valid CSRF token - cy.request( baseRestUrl + '/api/authn/status' ) - .then((response) => { - // We should receive a CSRF token returned in a response header - expect(response.headers).to.have.property('dspace-xsrf-token'); - const csrfToken = response.headers['dspace-xsrf-token']; + // Now find domain of our REST API, again with a fallback. + let baseDomain = FALLBACK_TEST_REST_DOMAIN; + if (!config.rest.host) { + console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN); + } else { + baseDomain = config.rest.host; + } - // Now, send login POST request including that CSRF token - cy.request({ - method: 'POST', - url: baseRestUrl + '/api/authn/login', - headers: { 'X-XSRF-TOKEN' : csrfToken}, - form: true, // indicates the body should be form urlencoded - body: { user: email, password: password } - }).then((resp) => { - // We expect a successful login - expect(resp.status).to.eq(200); - // We expect to have a valid authorization header returned (with our auth token) - expect(resp.headers).to.have.property('authorization'); + // Create a fake CSRF Token. Set it in the required server-side cookie + const csrfToken = 'fakeLoginCSRFToken'; + cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain }); - // Initialize our AuthTokenInfo object from the authorization header. - const authheader = resp.headers.authorization as string; - const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader); + // Now, send login POST request including that CSRF token + cy.request({ + method: 'POST', + url: baseRestUrl + '/api/authn/login', + headers: { [XSRF_REQUEST_HEADER]: csrfToken}, + form: true, // indicates the body should be form urlencoded + body: { user: email, password: password } + }).then((resp) => { + // We expect a successful login + expect(resp.status).to.eq(200); + // We expect to have a valid authorization header returned (with our auth token) + expect(resp.headers).to.have.property('authorization'); - // Save our AuthTokenInfo object to our dsAuthInfo UI cookie - // This ensures the UI will recognize we are logged in on next "visit()" - cy.setCookie(TOKENITEM, JSON.stringify(authinfo)); - }); + // Initialize our AuthTokenInfo object from the authorization header. + const authheader = resp.headers.authorization as string; + const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader); + + // Save our AuthTokenInfo object to our dsAuthInfo UI cookie + // This ensures the UI will recognize we are logged in on next "visit()" + cy.setCookie(TOKENITEM, JSON.stringify(authinfo)); }); + // Remove cookie with fake CSRF token, as it's no longer needed + cy.clearCookie(DSPACE_XSRF_COOKIE); }); } // Add as a Cypress command (i.e. assign to 'cy.login') Cypress.Commands.add('login', login); - /** * Login user via displayed login form * @param email email to login as * @param password password to login as */ - function loginViaForm(email: string, password: string): void { +function loginViaForm(email: string, password: string): void { // Enter email cy.get('ds-log-in [data-test="email"]').type(email); // Enter password @@ -111,3 +127,60 @@ Cypress.Commands.add('login', login); } // Add as a Cypress command (i.e. assign to 'cy.loginViaForm') Cypress.Commands.add('loginViaForm', loginViaForm); + + +/** + * Generate statistic view event for given object. Useful for testing statistics pages with + * pre-generated statistics. This just generates a single "hit", but can be called multiple times to + * generate multiple hits. + * @param uuid UUID of object + * @param dsoType type of DSpace Object (e.g. "item", "collection", "community") + */ +function generateViewEvent(uuid: string, dsoType: string): void { + // Cypress doesn't have access to the running application in Node.js. + // So, it's not possible to inject or load the AppConfig or environment of the Angular UI. + // Instead, we'll read our running application's config.json, which contains the configs & + // is regenerated at runtime each time the Angular UI application starts up. + cy.task('readUIConfig').then((str: string) => { + // Parse config into a JSON object + const config = JSON.parse(str); + + // Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found. + let baseRestUrl = FALLBACK_TEST_REST_BASE_URL; + if (!config.rest.baseUrl) { + console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL); + } else { + baseRestUrl = config.rest.baseUrl; + } + + // Now find domain of our REST API, again with a fallback. + let baseDomain = FALLBACK_TEST_REST_DOMAIN; + if (!config.rest.host) { + console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN); + } else { + baseDomain = config.rest.host; + } + + // Create a fake CSRF Token. Set it in the required server-side cookie + const csrfToken = 'fakeGenerateViewEventCSRFToken'; + cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain }); + + // Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header + cy.request({ + method: 'POST', + url: baseRestUrl + '/api/statistics/viewevents', + headers: { [XSRF_REQUEST_HEADER] : csrfToken}, + //form: true, // indicates the body should be form urlencoded + body: { targetId: uuid, targetType: dsoType } + }).then((resp) => { + // We expect a 201 (which means statistics event was created) + expect(resp.status).to.eq(201); + }); + + // Remove cookie with fake CSRF token, as it's no longer needed + cy.clearCookie(DSPACE_XSRF_COOKIE); + }); +} +// Add as a Cypress command (i.e. assign to 'cy.generateViewEvent') +Cypress.Commands.add('generateViewEvent', generateViewEvent); + diff --git a/src/app/core/auth/server-auth-request.service.spec.ts b/src/app/core/auth/server-auth-request.service.spec.ts index df6d78256b..5b0221e5df 100644 --- a/src/app/core/auth/server-auth-request.service.spec.ts +++ b/src/app/core/auth/server-auth-request.service.spec.ts @@ -8,7 +8,7 @@ import { PostRequest } from '../data/request.models'; import { XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER -} from '../xsrf/xsrf.interceptor'; +} from '../xsrf/xsrf.constants'; describe(`ServerAuthRequestService`, () => { let href: string; diff --git a/src/app/core/auth/server-auth-request.service.ts b/src/app/core/auth/server-auth-request.service.ts index d6302081bc..058322acce 100644 --- a/src/app/core/auth/server-auth-request.service.ts +++ b/src/app/core/auth/server-auth-request.service.ts @@ -13,7 +13,7 @@ import { XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER, DSPACE_XSRF_COOKIE -} from '../xsrf/xsrf.interceptor'; +} from '../xsrf/xsrf.constants'; import { map } from 'rxjs/operators'; import { Observable } from 'rxjs'; diff --git a/src/app/core/xsrf/xsrf.constants.ts b/src/app/core/xsrf/xsrf.constants.ts new file mode 100644 index 0000000000..64da5d674e --- /dev/null +++ b/src/app/core/xsrf/xsrf.constants.ts @@ -0,0 +1,33 @@ +/** + * XSRF / CSRF related constants + */ + +/** + * Name of CSRF/XSRF header we (client) may SEND in requests to backend. + * (This is a standard header name for XSRF/CSRF defined by Angular) + */ +export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN'; + +/** + * Name of CSRF/XSRF header we (client) may RECEIVE in responses from backend + * This header is defined by DSpace backend, see https://github.com/DSpace/RestContract/blob/main/csrf-tokens.md + */ +export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN'; + +/** + * Name of client-side Cookie where we store the CSRF/XSRF token between requests. + * This cookie is only available to client, and should be updated whenever a new XSRF_RESPONSE_HEADER + * is found in a response from the backend. + */ +export const XSRF_COOKIE = 'XSRF-TOKEN'; + +/** + * Name of server-side cookie the backend expects the XSRF token to be in. + * When the backend receives a modifying request, it will validate the CSRF/XSRF token by looking + * for a match between the XSRF_REQUEST_HEADER and this Cookie. For more details see + * https://github.com/DSpace/RestContract/blob/main/csrf-tokens.md + * + * NOTE: This Cookie is NOT readable to the client/UI. It is only readable to the backend and will + * be sent along automatically by the user's browser. + */ +export const DSPACE_XSRF_COOKIE = 'DSPACE-XSRF-COOKIE'; diff --git a/src/app/core/xsrf/xsrf.interceptor.ts b/src/app/core/xsrf/xsrf.interceptor.ts index cded432397..c728a5cbd0 100644 --- a/src/app/core/xsrf/xsrf.interceptor.ts +++ b/src/app/core/xsrf/xsrf.interceptor.ts @@ -12,15 +12,7 @@ import { Observable, throwError } from 'rxjs'; import { tap, catchError } from 'rxjs/operators'; import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; import { CookieService } from '../services/cookie.service'; - -// Name of XSRF header we may send in requests to backend (this is a standard name defined by Angular) -export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN'; -// Name of XSRF header we may receive in responses from backend -export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN'; -// Name of cookie where we store the XSRF token -export const XSRF_COOKIE = 'XSRF-TOKEN'; -// Name of cookie the backend expects the XSRF token to be in -export const DSPACE_XSRF_COOKIE = 'DSPACE-XSRF-COOKIE'; +import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from './xsrf.constants'; /** * Custom Http Interceptor intercepting Http Requests & Responses to diff --git a/src/app/shared/upload/uploader/uploader.component.ts b/src/app/shared/upload/uploader/uploader.component.ts index 14b1ca9b94..ef4ce4ee45 100644 --- a/src/app/shared/upload/uploader/uploader.component.ts +++ b/src/app/shared/upload/uploader/uploader.component.ts @@ -9,7 +9,7 @@ import { UploaderOptions } from './uploader-options.model'; import { hasValue, isNotEmpty, isUndefined } from '../../empty.util'; import { UploaderProperties } from './uploader-properties.model'; import { HttpXsrfTokenExtractor } from '@angular/common/http'; -import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from '../../../core/xsrf/xsrf.interceptor'; +import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from '../../../core/xsrf/xsrf.constants'; import { CookieService } from '../../../core/services/cookie.service'; import { DragService } from '../../../core/drag.service'; From 61ace6f8314c4d578143c4b709056a11c56ef030 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 Mar 2023 15:02:31 -0600 Subject: [PATCH 12/33] Bug fix: generateViewEvent() requires Solr to commit stats immediately. --- cypress/support/commands.ts | 12 ++++++++++-- docker/docker-compose-ci.yml | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 2d3947d07f..c70c4e37e1 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -133,6 +133,10 @@ Cypress.Commands.add('loginViaForm', loginViaForm); * Generate statistic view event for given object. Useful for testing statistics pages with * pre-generated statistics. This just generates a single "hit", but can be called multiple times to * generate multiple hits. + * + * NOTE: This requires that "solr-statistics.autoCommit=false" be set on the DSpace backend + * (as it is in our docker-compose-ci.yml used in CI). + * Otherwise, by default, new statistical events won't be saved until Solr's autocommit next triggers. * @param uuid UUID of object * @param dsoType type of DSpace Object (e.g. "item", "collection", "community") */ @@ -169,9 +173,13 @@ function generateViewEvent(uuid: string, dsoType: string): void { cy.request({ method: 'POST', url: baseRestUrl + '/api/statistics/viewevents', - headers: { [XSRF_REQUEST_HEADER] : csrfToken}, + headers: { + [XSRF_REQUEST_HEADER] : csrfToken, + // use a known public IP address to avoid being seen as a "bot" + 'X-Forwarded-For': '1.1.1.1', + }, //form: true, // indicates the body should be form urlencoded - body: { targetId: uuid, targetType: dsoType } + body: { targetId: uuid, targetType: dsoType }, }).then((resp) => { // We expect a 201 (which means statistics event was created) expect(resp.status).to.eq(201); diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index ef84c14f43..9ec8fe664a 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -30,6 +30,9 @@ services: db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace' # solr.server: Ensure we are using the 'dspacesolr' image for Solr solr__P__server: http://dspacesolr:8983/solr + # Tell Statistics to commit all views immediately instead of waiting on Solr's autocommit. + # This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly. + solr__D__statistics__P__autoCommit: 'false' depends_on: - dspacedb image: dspace/dspace:dspace-7_x-test From 6d1b9f3fa4d5d4b0fc23054a1d5ffd9c2d69b4a1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 Mar 2023 16:38:25 -0600 Subject: [PATCH 13/33] Ensure all SSR caching is disabled in CI environment --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3fe88a58b9..5470b3cc2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,9 @@ jobs: # Spin up UI on 127.0.0.1 to avoid host resolution issues in e2e tests with Node 18+ DSPACE_UI_HOST: 127.0.0.1 DSPACE_UI_PORT: 4000 + # Ensure all SSR caching is disabled in test environment + DSPACE_CACHE_SERVERSIDE_BOTCACHE_MAX: 0 + DSPACE_CACHE_SERVERSIDE_ANONYMOUSCACHE_MAX: 0 # Tell Cypress to run e2e tests using the same UI URL CYPRESS_BASE_URL: http://127.0.0.1:4000 # When Chrome version is specified, we pin to a specific version of Chrome From 1ca8529440a7ca4e70da004dd94f26b3bdaf3c65 Mon Sep 17 00:00:00 2001 From: cris Date: Fri, 10 Mar 2023 03:22:47 +0000 Subject: [PATCH 14/33] collection is not modifiable in workflowitem section --- src/app/submission/edit/submission-edit.component.html | 1 + src/app/submission/edit/submission-edit.component.ts | 7 ++++++- .../collection/submission-form-collection.component.html | 2 +- .../collection/submission-form-collection.component.ts | 7 +++++++ src/app/submission/form/submission-form.component.html | 1 + src/app/submission/form/submission-form.component.ts | 8 ++++++++ .../workflowitems-edit-page-routing.module.ts | 6 +++++- 7 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/app/submission/edit/submission-edit.component.html b/src/app/submission/edit/submission-edit.component.html index b74d3861f8..9c0e9eae72 100644 --- a/src/app/submission/edit/submission-edit.component.html +++ b/src/app/submission/edit/submission-edit.component.html @@ -5,5 +5,6 @@ [submissionDefinition]="submissionDefinition" [submissionErrors]="submissionErrors" [item]="item" + [collectionModifiable]="collectionModifiable" [submissionId]="submissionId">
diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index 840d57c661..915d0324be 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -34,6 +34,8 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * The collection id this submission belonging to * @type {string} */ + public collectionModifiable: boolean; + public collectionId: string; /** @@ -102,13 +104,16 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { private itemDataService: ItemDataService, private submissionService: SubmissionService, private translate: TranslateService, - private submissionJsonPatchOperationsService: SubmissionJsonPatchOperationsService) { + private submissionJsonPatchOperationsService: SubmissionJsonPatchOperationsService) { } /** * Retrieve workspaceitem/workflowitem from server and initialize all instance variables */ ngOnInit() { + + this.collectionModifiable = this.route.snapshot.data['collectionModifiable']; + this.subs.push( this.route.paramMap.pipe( switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), diff --git a/src/app/submission/form/collection/submission-form-collection.component.html b/src/app/submission/form/collection/submission-form-collection.component.html index 15b6ff280e..aa33905daf 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.html +++ b/src/app/submission/form/collection/submission-form-collection.component.html @@ -25,7 +25,7 @@ class="btn btn-outline-primary" (blur)="onClose()" (click)="onClose()" - [disabled]="(processingChange$ | async)" + [disabled]="(processingChange$ | async) || collectionModifiable" ngbDropdownToggle> {{ selectedCollectionName$ | async }} diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index adcffd9b5d..fb2b2a815a 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -51,6 +51,12 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { * @type {SubmissionDefinitionsModel} */ @Input() currentDefinition: string; + + /** + * Checks if the collection can be modifiable by the user + * @type {booelan} + */ + @Input() collectionModifiable: boolean; /** * The submission id @@ -144,6 +150,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { ngOnInit() { this.pathCombiner = new JsonPatchOperationPathCombiner('sections', 'collection'); this.available$ = this.sectionsService.isSectionTypeAvailable(this.submissionId, SectionsType.collection); + console.log("this.collectionModifiable", this.collectionModifiable) } /** diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index a80fe35f4e..f75af08f8e 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -11,6 +11,7 @@ diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index 42ee9f05ac..dba803e5c1 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -34,8 +34,16 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { * @type {string} */ @Input() collectionId: string; + @Input() item: Item; + /** + * Checks if the collection can be modifiable by the user + * @type {booelan} + */ + @Input() collectionModifiable: boolean; + + /** * The list of submission's sections * @type {WorkspaceitemSectionsObject} diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index 06536d5816..89c77f7c3d 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -34,7 +34,11 @@ import { resolve: { breadcrumb: I18nBreadcrumbResolver }, - data: { title: 'workflow-item.edit.title', breadcrumbKey: 'workflow-item.edit' } + data: { + title: 'workflow-item.edit.title', + breadcrumbKey: 'workflow-item.edit', + collectionModifiable: true + } }, { canActivate: [AuthenticatedGuard], From 1c2d96ce43024c279f79cef0828bae956df25a51 Mon Sep 17 00:00:00 2001 From: cris Date: Fri, 10 Mar 2023 03:39:03 +0000 Subject: [PATCH 15/33] solving lint issues --- src/app/submission/edit/submission-edit.component.ts | 6 +++--- .../form/collection/submission-form-collection.component.ts | 3 +-- .../workflowitems-edit-page-routing.module.ts | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index 915d0324be..426dfe7371 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -104,7 +104,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { private itemDataService: ItemDataService, private submissionService: SubmissionService, private translate: TranslateService, - private submissionJsonPatchOperationsService: SubmissionJsonPatchOperationsService) { + private submissionJsonPatchOperationsService: SubmissionJsonPatchOperationsService) { } /** @@ -112,8 +112,8 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { */ ngOnInit() { - this.collectionModifiable = this.route.snapshot.data['collectionModifiable']; - + this.collectionModifiable = this.route.snapshot.data.collectionModifiable; + this.subs.push( this.route.paramMap.pipe( switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index fb2b2a815a..695b910179 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -51,7 +51,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { * @type {SubmissionDefinitionsModel} */ @Input() currentDefinition: string; - + /** * Checks if the collection can be modifiable by the user * @type {booelan} @@ -150,7 +150,6 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { ngOnInit() { this.pathCombiner = new JsonPatchOperationPathCombiner('sections', 'collection'); this.available$ = this.sectionsService.isSectionTypeAvailable(this.submissionId, SectionsType.collection); - console.log("this.collectionModifiable", this.collectionModifiable) } /** diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index 89c77f7c3d..5d5a7443de 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -34,7 +34,7 @@ import { resolve: { breadcrumb: I18nBreadcrumbResolver }, - data: { + data: { title: 'workflow-item.edit.title', breadcrumbKey: 'workflow-item.edit', collectionModifiable: true From f561ff8f35ca83674e5565dc32b64422e0e1cfda Mon Sep 17 00:00:00 2001 From: cris Date: Sun, 12 Mar 2023 19:39:26 +0000 Subject: [PATCH 16/33] collectionModifiable can be null --- src/app/submission/edit/submission-edit.component.ts | 9 +++++++-- .../collection/submission-form-collection.component.ts | 2 +- src/app/submission/form/submission-form.component.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index 426dfe7371..c4ee49dbe8 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -34,9 +34,14 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * The collection id this submission belonging to * @type {string} */ - public collectionModifiable: boolean; - public collectionId: string; + + /** + * Checks if the collection can be modifiable by the user + * @type {booelan} + */ + public collectionModifiable: boolean | null = null; + /** * The list of submission's sections diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index 695b910179..4eac4c506a 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -56,7 +56,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { * Checks if the collection can be modifiable by the user * @type {booelan} */ - @Input() collectionModifiable: boolean; + @Input() collectionModifiable: boolean | null = null; /** * The submission id diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index dba803e5c1..0e17e128bc 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -41,7 +41,7 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { * Checks if the collection can be modifiable by the user * @type {booelan} */ - @Input() collectionModifiable: boolean; + @Input() collectionModifiable: boolean | null = null; /** From 22da2f7160152f97d249447a75079c596de75d6e Mon Sep 17 00:00:00 2001 From: cris Date: Sun, 12 Mar 2023 19:49:30 +0000 Subject: [PATCH 17/33] avoiding lint issues --- src/app/submission/edit/submission-edit.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index c4ee49dbe8..bb8f7f9cc4 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -35,7 +35,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * @type {string} */ public collectionId: string; - + /** * Checks if the collection can be modifiable by the user * @type {booelan} From 12518dcc86dd47246072ecd458a6c5af423b7e0e Mon Sep 17 00:00:00 2001 From: cris Date: Sun, 12 Mar 2023 20:49:01 +0000 Subject: [PATCH 18/33] if collectionModiable does not exists is set as null --- src/app/submission/edit/submission-edit.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index bb8f7f9cc4..9e4873ddf3 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -117,7 +117,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { */ ngOnInit() { - this.collectionModifiable = this.route.snapshot.data.collectionModifiable; + this.collectionModifiable = this.route.snapshot.data.collectionModifiable ?? null; this.subs.push( this.route.paramMap.pipe( From 6983b401362905e4fc56767ffdf028ab21988d7f Mon Sep 17 00:00:00 2001 From: cris Date: Sun, 12 Mar 2023 21:13:17 +0000 Subject: [PATCH 19/33] if data does not exist the undefined error is avoid --- src/app/submission/edit/submission-edit.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index 9e4873ddf3..93200e5e5c 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -117,7 +117,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { */ ngOnInit() { - this.collectionModifiable = this.route.snapshot.data.collectionModifiable ?? null; + this.collectionModifiable = this.route.snapshot.data?.collectionModifiable ?? null; this.subs.push( this.route.paramMap.pipe( From d60e3587667634283922ff96aef059f53ec6bde2 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 14 Mar 2023 22:14:01 +0100 Subject: [PATCH 20/33] Fix media viewer thumbnails exceeding max width --- .../media-viewer-image.component.scss | 24 +++++++++++++++---- .../media-viewer-video.component.scss | 10 ++++++-- .../publication/publication.component.html | 4 ++-- .../untyped-item/untyped-item.component.html | 4 ++-- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/app/item-page/media-viewer/media-viewer-image/media-viewer-image.component.scss b/src/app/item-page/media-viewer/media-viewer-image/media-viewer-image.component.scss index 72ce4b04d9..cba963b6fa 100644 --- a/src/app/item-page/media-viewer/media-viewer-image/media-viewer-image.component.scss +++ b/src/app/item-page/media-viewer/media-viewer-image/media-viewer-image.component.scss @@ -1,6 +1,20 @@ -.ngx-gallery { - display: inline-block; - margin-bottom: 20px; - width: 340px !important; - height: 279px !important; +:host ::ng-deep { + .ngx-gallery { + width: unset !important; + height: unset !important; + } + + ngx-gallery-image { + max-width: 340px !important; + + .ngx-gallery-image { + background-position: left; + } + } + + ngx-gallery-image:after { + padding-top: 75%; + display: block; + content: ''; + } } diff --git a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.scss b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.scss index 7702da7361..bb8b9d360e 100644 --- a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.scss +++ b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.scss @@ -1,4 +1,10 @@ video { - width: 340px; - height: 279px; + width: 100%; + height: auto; + max-width: 340px; +} + +.buttons { + display: flex; + gap: .25rem; } diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index bace9fcd0a..658d637107 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -22,9 +22,9 @@ - +
- +
- +
- +
Date: Fri, 17 Mar 2023 15:02:07 -0400 Subject: [PATCH 21/33] Update to fixe french labels for System-wide alert Add translations for the System wide alert feature as well other various missing parameters. --- src/assets/i18n/fr.json5 | 183 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 1 deletion(-) diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 1e042c81fd..34e352b790 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -951,6 +951,9 @@ // "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.", "bitstream-request-a-copy.submit.error": "Un problème est survenu lors de la soumission de la demande d'Item", + + //"browse.back.all-results": "All browse results", + "browse.back.all-results": "Tous les résultats", // "browse.comcol.by.author": "By Author", "browse.comcol.by.author": "Auteur", @@ -1068,9 +1071,18 @@ // "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}", "browse.title": "Parcourir la collection {{ collection }} par {{ field }} {{ value }}", + + //"browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}", + "browse.title.page": "Parcourir la collection {{ collection }} par {{ field }} {{ value }}", + + //"search.browse.item-back": "Back to Results", + "search.browse.item-back": "Retour aux résultats", // "chips.remove": "Remove chip", "chips.remove": "Supprimer fragment", + + //"claimed-approved-search-result-list-element.title": "Approved", + "claimed-approved-search-result-list-element.title": "Approuvé", // "collection.create.head": "Create a Collection", "collection.create.head": "Créer une collection", @@ -1718,6 +1730,9 @@ // "cookies.consent.decline": "Decline", "cookies.consent.decline": "Refuser", + + //"cookies.consent.ok": "That's ok", + "cookies.consent.ok": "Accepter", // "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: Authentication, Preferences, Acknowledgement and Statistics.
To learn more, please read our {privacyPolicy}.", "cookies.consent.content-notice.description": "Vos données personnelles sont récupérées et utilisées dans les contextes suivants : authentification, préférences, consentement et statistiques.
Pour plus d'informations, veuillez vous référer à la {privacyPolicy}.", @@ -1883,6 +1898,9 @@ // "dso-selector.set-scope.community.button": "Search all of DSpace", "dso-selector.set-scope.community.button": "Chercher dans toutes les collections", + + //"dso-selector.set-scope.community.or-divider": "or", + "dso-selector.set-scope.community.or-divider": "ou", // "dso-selector.set-scope.community.input-header": "Search for a community or collection", "dso-selector.set-scope.community.input-header": "Chercher une communauté ou une collection", @@ -2495,6 +2513,71 @@ // "item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper", "item.edit.tabs.item-mapper.title": "Édition d'Item - Association de collection", + + // "item.edit.identifiers.doi.status.UNKNOWN": "Unknown", + "item.edit.identifiers.doi.status.UNKNOWN": "Inconnu", + + // "item.edit.identifiers.doi.status.TO_BE_REGISTERED": "Queued for registration", + "item.edit.identifiers.doi.status.TO_BE_REGISTERED": "En attente d'inscription", + + // "item.edit.identifiers.doi.status.TO_BE_RESERVED": "Queued for reservation", + "item.edit.identifiers.doi.status.TO_BE_RESERVED": "En attente de réservation", + + // "item.edit.identifiers.doi.status.IS_REGISTERED": "Registered", + "item.edit.identifiers.doi.status.IS_REGISTERED": "Inscrit", + + // "item.edit.identifiers.doi.status.IS_RESERVED": "Reserved", + "item.edit.identifiers.doi.status.IS_RESERVED": "Réseré", + + // "item.edit.identifiers.doi.status.UPDATE_RESERVED": "Reserved (update queued)", + "item.edit.identifiers.doi.status.UPDATE_RESERVED": "Réservé (en attente)", + + // "item.edit.identifiers.doi.status.UPDATE_REGISTERED": "Registered (update queued)", + "item.edit.identifiers.doi.status.UPDATE_REGISTERED": "Inscrit (en attente)", + + // "item.edit.identifiers.doi.status.UPDATE_BEFORE_REGISTRATION": "Queued for update and registration", + "item.edit.identifiers.doi.status.UPDATE_BEFORE_REGISTRATION": "En attente pour la mise à jour et l'inscription", + + // "item.edit.identifiers.doi.status.TO_BE_DELETED": "Queued for deletion", + "item.edit.identifiers.doi.status.TO_BE_DELETED": "En attente pour la suppression", + + // "item.edit.identifiers.doi.status.DELETED": "Deleted", + "item.edit.identifiers.doi.status.DELETED": "Supprimé", + + // "item.edit.identifiers.doi.status.PENDING": "Pending (not registered)", + "item.edit.identifiers.doi.status.PENDING": "En attente (non inscrit)", + + // "item.edit.identifiers.doi.status.MINTED": "Minted (not registered)", + "item.edit.identifiers.doi.status.MINTED": "Émis (non inscrit)", + + // "item.edit.tabs.status.buttons.register-doi.label": "Register a new or pending DOI", + "item.edit.tabs.status.buttons.register-doi.label": "Inscrire un nouveau DOI ou un DOI en attente", + + // "item.edit.tabs.status.buttons.register-doi.button": "Register DOI...", + "item.edit.tabs.status.buttons.register-doi.button": "Inscrire le DOI...", + + // "item.edit.register-doi.header": "Register a new or pending DOI", + "item.edit.register-doi.header": "Inscrire un nouveau DOI ou un DOI en attente", + + // "item.edit.register-doi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out", + "item.edit.register-doi.description": "Réviser les identifiants en attente and les métadonnées ci-dessous item metadata below puis cliquer sur Confirmer afin de lancer l'inscription du DOI ou sur Annuler pour interrompre l'inscription", + + // "item.edit.register-doi.confirm": "Confirm", + "item.edit.register-doi.confirm": "Confirmer", + + // "item.edit.register-doi.cancel": "Cancel", + "item.edit.register-doi.cancel": "Annuler", + + // "item.edit.register-doi.success": "DOI queued for registration successfully.", + "item.edit.register-doi.success": "DOI mis en attente pour l'inscription.", + + // "item.edit.register-doi.error": "Error registering DOI", + "item.edit.register-doi.error": "Erreur lors de l'inscription du DOI", + + // "item.edit.register-doi.to-update": "The following DOI has already been minted and will be queued for registration online", + "item.edit.register-doi.to-update": "Le DOI suivant a déjà été généré et sera mis en attente pour être inscrit", + + // "item.edit.item-mapper.buttons.add": "Map item to selected collections", "item.edit.item-mapper.buttons.add": "Associer l'Item aux collections sélectionnées", @@ -3738,6 +3821,9 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Vos dépôts", + + //"mydspace.show.supervisedWorkspace": "Supervised items", + "mydspace.show.supervisedWorkspace": "Items supervisés", // "mydspace.status.archived": "Archived", "mydspace.status.archived": "Archivés", @@ -3804,6 +3890,9 @@ // "nav.stop-impersonating": "Stop impersonating EPerson", "nav.stop-impersonating": "Retour à votre propre EPerson", + + //"nav.subscriptions" : "Subscriptions", + "nav.subscriptions" : "Abonnements", // "nav.toggle": "Toggle navigation", "nav.toggle": "Basculer la navigation", @@ -5060,6 +5149,9 @@ // "submission.import-external.source.crossref": "CrossRef", "submission.import-external.source.crossref": "CrossRef (DOI)", + + //"submission.import-external.source.datacite": "DataCite", + "submission.import-external.source.datacite": "DataCite (DOI)", // "submission.import-external.source.scielo": "SciELO", "submission.import-external.source.scielo": "SciELO", @@ -5075,6 +5167,9 @@ // "submission.import-external.source.orcidWorks": "ORCID", "submission.import-external.source.orcidWorks": "ORCID", + + //"submission.import-external.source.epo": "European Patent Office (EPO)", + "submission.import-external.source.epo": "Office Européen des brevets (OEB)", // "submission.import-external.source.loading": "Loading ...", "submission.import-external.source.loading": "En cours de chargement ...", @@ -5991,14 +6086,24 @@ // "virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata", "virtual-metadata.delete-relationship.modal-head": "Sélectionner les Items pour lesquels vous souhaitez sauvegarder les métadonnées virtuelles en tant que métadonnées réelles", + + //"supervisedWorkspace.search.results.head": "Supervised Items", + "supervisedWorkspace.search.results.head": "Items supervisés", + + //"workspace.search.results.head": "Your submissions", + "workspace.search.results.head": "Vos dépôts", + // "workspace.search.results.head": "Your submissions", - "workspace.search.results.head": "Vos soumissions", + "workspace.search.results.head": "Vos dépôts", // "workflowAdmin.search.results.head": "Administer Workflow", "workflowAdmin.search.results.head": "Workflow Administrateur", // "workflow.search.results.head": "Workflow tasks", "workflow.search.results.head": "Tableau de suivi", + + //"supervision.search.results.head": "Workflow and Workspace tasks", + "supervision.search.results.head": "Tableau de suivi et dépôts en cours", // "workflow-item.edit.breadcrumbs": "Edit workflowitem", "workflow-item.edit.breadcrumbs": "Éditer l'Item du Workflow", @@ -6074,5 +6179,81 @@ // "idle-modal.extend-session": "Extend session" "idle-modal.extend-session": "Prolonger la session" + + + + // "system-wide-alert-banner.retrieval.error": "Something went wrong retrieving the system-wide alert banner", + "system-wide-alert-banner.retrieval.error": "Une erreur s'est produite lors de la récupération de la bannière du message d'avertissement", + + //"system-wide-alert-banner.countdown.prefix": "In", + "system-wide-alert-banner.countdown.prefix": "Dans", + +// "system-wide-alert-banner.countdown.days": "{{days}} day(s),", + "system-wide-alert-banner.countdown.days": "{{days}} jour(s),", + + // "system-wide-alert-banner.countdown.hours": "{{hours}} hour(s) and", +"system-wide-alert-banner.countdown.hours": "{{hours}} heure(s) et", + + // "system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):", + "system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):", + + + + // "menu.section.system-wide-alert": "System-wide Alert", + "menu.section.system-wide-alert": "Messages d'avertissement", + + // "system-wide-alert.form.header": "System-wide Alert", +"system-wide-alert.form.header": "Messages d'avertissement", + + // "system-wide-alert-form.retrieval.error": "Something went wrong retrieving the system-wide alert", + "system-wide-alert-form.retrieval.error": "Une erreur s'est produite lors de la récupération de la bannière du message d'avertissement", + + //"system-wide-alert.form.cancel": "Cancel", + "system-wide-alert.form.cancel": "Annuler", + + + "system-wide-alert.form.save": "Save", +"system-wide-alert.form.save": "Sauvegarder", + + "system-wide-alert.form.label.active": "ACTIVE", + "system-wide-alert.form.label.active": "ACTIF", + + "system-wide-alert.form.label.inactive": "INACTIVE", + "system-wide-alert.form.label.inactive": "INACTIF", + + "system-wide-alert.form.error.message": "The system wide alert must have a message", + "system-wide-alert.form.error.message": "Le message d'avertissement ne peut pas être vide", + + "system-wide-alert.form.label.message": "Alert message", + "system-wide-alert.form.label.message": "Message d'avertissement", + + "system-wide-alert.form.label.countdownTo.enable": "Enable a countdown timer", + "system-wide-alert.form.label.countdownTo.enable": "Activer un compte à rebours", + + "system-wide-alert.form.label.countdownTo.hint": "Hint: Set a countdown timer. When enabled, a date can be set in the future and the system-wide alert banner will perform a countdown to the set date. When this timer ends, it will disappear from the alert. The server will NOT be automatically stopped.", + "system-wide-alert.form.label.countdownTo.hint": "Lorsque cette option est activée, il est possible de fixer une date dans le futur et la bannière du message d'avertissement effectuera un compte à rebours jusqu'à la date fixée. À la fin du compte à rebours, le message d'avertissement disparaîtra mais le serveur ne sera pas arrêté automatiquement.", + + "system-wide-alert.form.label.preview": "System-wide alert preview", + "system-wide-alert.form.label.preview": "Aperçu du message d'avertissement", + + "system-wide-alert.form.update.success": "The system-wide alert was successfully updated", + "system-wide-alert.form.update.success": "Le message d'avertissement a été mis à jour", + + "system-wide-alert.form.update.error": "Something went wrong when updating the system-wide alert", + "system-wide-alert.form.update.error": "Un erreur s'est produite lors de la mise à jour du message d'avertissement", + + "system-wide-alert.form.create.success": "The system-wide alert was successfully created", + "system-wide-alert.form.create.success": "Le message d'avertissement a été crée", + + "system-wide-alert.form.create.error": "Something went wrong when creating the system-wide alert", + "system-wide-alert.form.create.error": "Un erreur s'est produite lors de la création du message d'avertissement", + + "admin.system-wide-alert.breadcrumbs": "System-wide Alerts", + "admin.system-wide-alert.breadcrumbs": "Messages d'avertissement", + + "admin.system-wide-alert.title": "System-wide Alerts", + "admin.system-wide-alert.title": "Messages d'avertissement", + + } From 46a73e9c1b71416fffac307e660bd3d9c9f3f560 Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Fri, 24 Mar 2023 14:15:07 -0400 Subject: [PATCH 22/33] Fixing minor syntax problems Added missing commas and missing "//" characters. --- src/assets/i18n/fr.json5 | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 34e352b790..32b2281921 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -6177,8 +6177,8 @@ // "idle-modal.log-out": "Log out", "idle-modal.log-out": "Déconnexion", - // "idle-modal.extend-session": "Extend session" - "idle-modal.extend-session": "Prolonger la session" + // "idle-modal.extend-session": "Extend session", + "idle-modal.extend-session": "Prolonger la session", @@ -6212,46 +6212,46 @@ "system-wide-alert.form.cancel": "Annuler", - "system-wide-alert.form.save": "Save", + //"system-wide-alert.form.save": "Save", "system-wide-alert.form.save": "Sauvegarder", - "system-wide-alert.form.label.active": "ACTIVE", + //"system-wide-alert.form.label.active": "ACTIVE", "system-wide-alert.form.label.active": "ACTIF", - "system-wide-alert.form.label.inactive": "INACTIVE", + //"system-wide-alert.form.label.inactive": "INACTIVE", "system-wide-alert.form.label.inactive": "INACTIF", - "system-wide-alert.form.error.message": "The system wide alert must have a message", + //"system-wide-alert.form.error.message": "The system wide alert must have a message", "system-wide-alert.form.error.message": "Le message d'avertissement ne peut pas être vide", - "system-wide-alert.form.label.message": "Alert message", + //"system-wide-alert.form.label.message": "Alert message", "system-wide-alert.form.label.message": "Message d'avertissement", - "system-wide-alert.form.label.countdownTo.enable": "Enable a countdown timer", + //"system-wide-alert.form.label.countdownTo.enable": "Enable a countdown timer", "system-wide-alert.form.label.countdownTo.enable": "Activer un compte à rebours", - "system-wide-alert.form.label.countdownTo.hint": "Hint: Set a countdown timer. When enabled, a date can be set in the future and the system-wide alert banner will perform a countdown to the set date. When this timer ends, it will disappear from the alert. The server will NOT be automatically stopped.", + //"system-wide-alert.form.label.countdownTo.hint": "Hint: Set a countdown timer. When enabled, a date can be set in the future and the system-wide alert banner will perform a countdown to the set date. When this timer ends, it will disappear from the alert. The server will NOT be automatically stopped.", "system-wide-alert.form.label.countdownTo.hint": "Lorsque cette option est activée, il est possible de fixer une date dans le futur et la bannière du message d'avertissement effectuera un compte à rebours jusqu'à la date fixée. À la fin du compte à rebours, le message d'avertissement disparaîtra mais le serveur ne sera pas arrêté automatiquement.", - "system-wide-alert.form.label.preview": "System-wide alert preview", + //"system-wide-alert.form.label.preview": "System-wide alert preview", "system-wide-alert.form.label.preview": "Aperçu du message d'avertissement", - "system-wide-alert.form.update.success": "The system-wide alert was successfully updated", + //"system-wide-alert.form.update.success": "The system-wide alert was successfully updated", "system-wide-alert.form.update.success": "Le message d'avertissement a été mis à jour", - "system-wide-alert.form.update.error": "Something went wrong when updating the system-wide alert", + //"system-wide-alert.form.update.error": "Something went wrong when updating the system-wide alert", "system-wide-alert.form.update.error": "Un erreur s'est produite lors de la mise à jour du message d'avertissement", - "system-wide-alert.form.create.success": "The system-wide alert was successfully created", + //"system-wide-alert.form.create.success": "The system-wide alert was successfully created", "system-wide-alert.form.create.success": "Le message d'avertissement a été crée", - "system-wide-alert.form.create.error": "Something went wrong when creating the system-wide alert", + //"system-wide-alert.form.create.error": "Something went wrong when creating the system-wide alert", "system-wide-alert.form.create.error": "Un erreur s'est produite lors de la création du message d'avertissement", - "admin.system-wide-alert.breadcrumbs": "System-wide Alerts", + //"admin.system-wide-alert.breadcrumbs": "System-wide Alerts", "admin.system-wide-alert.breadcrumbs": "Messages d'avertissement", - "admin.system-wide-alert.title": "System-wide Alerts", + //"admin.system-wide-alert.title": "System-wide Alerts", "admin.system-wide-alert.title": "Messages d'avertissement", From a2aa436c1dc143e706e73a7185e448a5d726a6b0 Mon Sep 17 00:00:00 2001 From: reetagithub <51482276+reetagithub@users.noreply.github.com> Date: Tue, 28 Mar 2023 10:24:01 +0300 Subject: [PATCH 23/33] Update en.json5 Two typos corrected. --- src/assets/i18n/en.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a964c9f1d1..c841875443 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2680,7 +2680,7 @@ "itemtemplate.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - "itemtemplate.edit.metadata.notifications.discarded.title": "Changed discarded", + "itemtemplate.edit.metadata.notifications.discarded.title": "Changes discarded", "itemtemplate.edit.metadata.notifications.error.title": "An error occurred", @@ -2690,7 +2690,7 @@ "itemtemplate.edit.metadata.notifications.outdated.content": "The item template you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "itemtemplate.edit.metadata.notifications.outdated.title": "Changed outdated", + "itemtemplate.edit.metadata.notifications.outdated.title": "Changes outdated", "itemtemplate.edit.metadata.notifications.saved.content": "Your changes to this item template's metadata were saved.", From 26f9c7e40f90fbe5f6c123d82ad661f4d651ab47 Mon Sep 17 00:00:00 2001 From: reetagithub <51482276+reetagithub@users.noreply.github.com> Date: Tue, 28 Mar 2023 12:44:34 +0300 Subject: [PATCH 24/33] Update fi.json5 Harmonized some translations and translated the few new translations. --- src/assets/i18n/fi.json5 | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5 index 05ae06d46b..46af219036 100644 --- a/src/assets/i18n/fi.json5 +++ b/src/assets/i18n/fi.json5 @@ -156,7 +156,8 @@ // "admin.registries.bitstream-formats.table.name": "Name", "admin.registries.bitstream-formats.table.name": "Nimi", - // TODO New key - Add a translation + + // "admin.registries.bitstream-formats.table.id" : "ID", "admin.registries.bitstream-formats.table.id" : "ID", // "admin.registries.bitstream-formats.table.return": "Return", @@ -207,7 +208,7 @@ "admin.registries.metadata.schemas.table.delete": "Poista valittu", // "admin.registries.metadata.schemas.table.id": "ID", - "admin.registries.metadata.schemas.table.id": "ID-tunnus", + "admin.registries.metadata.schemas.table.id": "ID", // "admin.registries.metadata.schemas.table.name": "Name", "admin.registries.metadata.schemas.table.name": "Nimi", @@ -237,7 +238,8 @@ // "admin.registries.schema.fields.table.field": "Field", "admin.registries.schema.fields.table.field": "Kenttä", - // TODO New key - Add a translation + + // "admin.registries.schema.fields.table.id" : "ID", "admin.registries.schema.fields.table.id" : "ID", // "admin.registries.schema.fields.table.scopenote": "Scope Note", @@ -408,7 +410,7 @@ "admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Jäsenenä näissä ryhmissä:", // "admin.access-control.epeople.form.table.id": "ID", - "admin.access-control.epeople.form.table.id": "ID-tunnus", + "admin.access-control.epeople.form.table.id": "ID", // "admin.access-control.epeople.form.table.name": "Name", "admin.access-control.epeople.form.table.name": "Nimi", @@ -2495,7 +2497,7 @@ "item.edit.tabs.status.buttons.reinstate.label": "Palauta tietue arkistoon", // "item.edit.tabs.status.buttons.withdraw.button": "Withdraw this item", - "item.edit.tabs.status.buttons.withdraw.button": "poista tämä kohde", + "item.edit.tabs.status.buttons.withdraw.button": "Poista tämä kohde", // "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository", "item.edit.tabs.status.buttons.withdraw.label": "Poista tietue käytöstä", @@ -2510,7 +2512,7 @@ "item.edit.tabs.status.labels.handle": "Handle-tunnus", // "item.edit.tabs.status.labels.id": "Item Internal ID", - "item.edit.tabs.status.labels.id": "Tietueen sisäinen ID-tunnus", + "item.edit.tabs.status.labels.id": "Tietueen sisäinen ID", // "item.edit.tabs.status.labels.itemPage": "Item Page", "item.edit.tabs.status.labels.itemPage": "Tietueen tiedot", @@ -3351,7 +3353,7 @@ "orgunit.page.edit": "Muokkaa tietuetta", // "orgunit.page.id": "ID", - "orgunit.page.id": "ID-tunnus", + "orgunit.page.id": "ID", // "orgunit.page.titleprefix": "Organizational Unit: ", "orgunit.page.titleprefix": "Organisaatioyksikkö: ", @@ -3403,7 +3405,7 @@ "person.page.orcid": "ORCID-tunniste", // "person.page.staffid": "Staff ID", - "person.page.staffid": "Henkilökunnan ID-tunnus", + "person.page.staffid": "Henkilökunnan ID", // "person.page.titleprefix": "Person: ", "person.page.titleprefix": "Käyttäjä: ", @@ -3655,7 +3657,7 @@ "project.page.funder": "Rahoittajat", // "project.page.id": "ID", - "project.page.id": "ID-tunnus", + "project.page.id": "ID", // "project.page.keyword": "Keywords", "project.page.keyword": "Asiasanat", @@ -3939,7 +3941,7 @@ "resource-policies.form.eperson-group-list.table.headers.action": "Toimenpide", // "resource-policies.form.eperson-group-list.table.headers.id": "ID", - "resource-policies.form.eperson-group-list.table.headers.id": "ID-tunnus", + "resource-policies.form.eperson-group-list.table.headers.id": "ID", // "resource-policies.form.eperson-group-list.table.headers.name": "Name", "resource-policies.form.eperson-group-list.table.headers.name": "Nimi", @@ -3987,7 +3989,7 @@ "resource-policies.table.headers.group": "Ryhmä", // "resource-policies.table.headers.id": "ID", - "resource-policies.table.headers.id": "ID-tunnus", + "resource-policies.table.headers.id": "ID", // "resource-policies.table.headers.name": "Name", "resource-policies.table.headers.name": "Nimi", @@ -4914,8 +4916,8 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Yksittäisten tiedostojen pääsyrajoitusten lisäksi {{collectionName}}-kokoelmaan ladatut tiedostot ovat seuraavien ryhmien saatavilla:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", - "submission.sections.upload.info": "Tietueen kaikki tiedostot on lueteltu tässä. Voit päivittää tiedoston metadataa ja pääsyehtoja tai ladata lisää tiedostoja raahaamalla ne mihin hyvänsä sivun kohtaan", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + "submission.sections.upload.info": "Tietueen kaikki tiedostot on lueteltu tässä. Voit päivittää tiedoston metadataa ja pääsyehtoja tai ladata lisää tiedostoja raahaamalla ne mihin hyvänsä sivun kohtaan.", // "submission.sections.upload.no-entry": "No", "submission.sections.upload.no-entry": "Ei", @@ -5066,8 +5068,7 @@ "uploader.or": " tai", // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", - // TODO Source message changed - Revise the translation - "uploader.processing": "Käsitellään", + "uploader.processing": "Käsitellään ladattuja tiedostoja... (voit sulkea tämän sivun)", // "uploader.queue-length": "Queue length", "uploader.queue-length": "Jonon pituus", From 0fbcf841259ea79418bc9ba81b45e44e89830369 Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Thu, 30 Mar 2023 11:50:16 -0400 Subject: [PATCH 25/33] Small updates to french translations Adding corrections suggested in the following PR review: https://github.com/DSpace/dspace-angular/pull/2154 --- src/assets/i18n/fr.json5 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 32b2281921..3ac72d796a 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -2527,7 +2527,7 @@ "item.edit.identifiers.doi.status.IS_REGISTERED": "Inscrit", // "item.edit.identifiers.doi.status.IS_RESERVED": "Reserved", - "item.edit.identifiers.doi.status.IS_RESERVED": "Réseré", + "item.edit.identifiers.doi.status.IS_RESERVED": "Réservé", // "item.edit.identifiers.doi.status.UPDATE_RESERVED": "Reserved (update queued)", "item.edit.identifiers.doi.status.UPDATE_RESERVED": "Réservé (en attente)", @@ -2560,7 +2560,7 @@ "item.edit.register-doi.header": "Inscrire un nouveau DOI ou un DOI en attente", // "item.edit.register-doi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out", - "item.edit.register-doi.description": "Réviser les identifiants en attente and les métadonnées ci-dessous item metadata below puis cliquer sur Confirmer afin de lancer l'inscription du DOI ou sur Annuler pour interrompre l'inscription", + "item.edit.register-doi.description": "Réviser les identifiants en attente et les métadonnées ci-dessous puis cliquer sur Confirmer afin de lancer l'inscription du DOI ou sur Annuler pour interrompre l'inscription", // "item.edit.register-doi.confirm": "Confirm", "item.edit.register-doi.confirm": "Confirmer", @@ -6088,7 +6088,7 @@ //"supervisedWorkspace.search.results.head": "Supervised Items", - "supervisedWorkspace.search.results.head": "Items supervisés", + "supervisedWorkspace.search.results.head": "Documents supervisés", //"workspace.search.results.head": "Your submissions", "workspace.search.results.head": "Vos dépôts", From b5881a0ae997d612d7cd1ba38e28743df0f91bff Mon Sep 17 00:00:00 2001 From: cris Date: Thu, 30 Mar 2023 16:51:08 +0000 Subject: [PATCH 26/33] modifying behavior of collectionModifiable --- .../form/collection/submission-form-collection.component.html | 2 +- .../workflowitems-edit-page-routing.module.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/submission/form/collection/submission-form-collection.component.html b/src/app/submission/form/collection/submission-form-collection.component.html index aa33905daf..c1227eeccc 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.html +++ b/src/app/submission/form/collection/submission-form-collection.component.html @@ -25,7 +25,7 @@ class="btn btn-outline-primary" (blur)="onClose()" (click)="onClose()" - [disabled]="(processingChange$ | async) || collectionModifiable" + [disabled]="(processingChange$ | async) || collectionModifiable == false" ngbDropdownToggle> {{ selectedCollectionName$ | async }} diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index 5d5a7443de..b093f20563 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -37,7 +37,7 @@ import { data: { title: 'workflow-item.edit.title', breadcrumbKey: 'workflow-item.edit', - collectionModifiable: true + collectionModifiable: false } }, { From 0ffdda26dca858302c35d752241212ecb23a894d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristian=20Emanuelle=20Guzm=C3=A1n=20Su=C3=A1rez?= Date: Thu, 30 Mar 2023 11:16:28 -0600 Subject: [PATCH 27/33] Update workflowitems-edit-page-routing.module.ts small change to re run test --- .../workflowitems-edit-page-routing.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index b093f20563..511009991a 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -62,7 +62,7 @@ import { { canActivate: [AuthenticatedGuard], path: WORKFLOW_ITEM_SEND_BACK_PATH, - component: ThemedWorkflowItemSendBackComponent, + component: ThemedWorkflowItemSendBackComponent, resolve: { breadcrumb: I18nBreadcrumbResolver }, From bb8775a8f0fb7ad6311e1f1078e5b30255db217a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristian=20Emanuelle=20Guzm=C3=A1n=20Su=C3=A1rez?= Date: Thu, 30 Mar 2023 12:02:42 -0600 Subject: [PATCH 28/33] Update workflowitems-edit-page-routing.module.ts avoiding trailing-space --- .../workflowitems-edit-page-routing.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index 511009991a..b093f20563 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -62,7 +62,7 @@ import { { canActivate: [AuthenticatedGuard], path: WORKFLOW_ITEM_SEND_BACK_PATH, - component: ThemedWorkflowItemSendBackComponent, + component: ThemedWorkflowItemSendBackComponent, resolve: { breadcrumb: I18nBreadcrumbResolver }, From 5b33c49ccc76c9c8813bbd565fc01da9a09b2b26 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 28 Mar 2023 13:18:34 -0700 Subject: [PATCH 29/33] Added upper limit to browse by date --- .../browse-by-date-page.component.spec.ts | 31 +++++++--- .../browse-by-date-page.component.ts | 57 ++++++++++++------- src/app/core/browse/browse.service.ts | 7 ++- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts index 5c2a6d820e..e41d3a45b2 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts @@ -22,6 +22,7 @@ import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { APP_CONFIG } from '../../../config/app-config.interface'; import { environment } from '../../../environments/environment'; +import { SortDirection } from '../../core/cache/models/sort-options.model'; describe('BrowseByDatePageComponent', () => { let comp: BrowseByDatePageComponent; @@ -49,12 +50,22 @@ describe('BrowseByDatePageComponent', () => { ] } }); + const lastItem = Object.assign(new Item(), { + id: 'last-item-id', + metadata: { + 'dc.date.issued': [ + { + value: '1960-01-01' + } + ] + } + }); - const mockBrowseService = { - getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData([]), - getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData([firstItem]), - getFirstItemFor: () => createSuccessfulRemoteDataObject$(firstItem) - }; + const mockBrowseService = { + getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData([]), + getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData([firstItem]), + getFirstItemFor: (definition: string, scope?: string, sortDirection?: SortDirection) => null + }; const mockDsoService = { findById: () => createSuccessfulRemoteDataObject$(mockCommunity) @@ -91,9 +102,14 @@ describe('BrowseByDatePageComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(BrowseByDatePageComponent); + const browseService = fixture.debugElement.injector.get(BrowseService); + spyOn(browseService, 'getFirstItemFor') + // ok to expect the default browse as first param since we just need the mock items obtained via sort direction. + .withArgs('author', undefined, SortDirection.ASC).and.returnValue(createSuccessfulRemoteDataObject$(firstItem)) + .withArgs('author', undefined, SortDirection.DESC).and.returnValue(createSuccessfulRemoteDataObject$(lastItem)); comp = fixture.componentInstance; - fixture.detectChanges(); route = (comp as any).route; + fixture.detectChanges(); }); it('should initialize the list of items', () => { @@ -107,6 +123,7 @@ describe('BrowseByDatePageComponent', () => { }); it('should create a list of startsWith options with the current year first', () => { - expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear()); + //expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear()); + expect(comp.startsWithOptions[0]).toEqual(1960); }); }); diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index ffa7e882af..01cd3a003e 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,11 +1,10 @@ import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { BrowseByMetadataPageComponent, - browseParamsToOptions, getBrowseSearchOptions + browseParamsToOptions, + getBrowseSearchOptions } from '../browse-by-metadata-page/browse-by-metadata-page.component'; import { combineLatest as observableCombineLatest } from 'rxjs'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { BrowseService } from '../../core/browse/browse.service'; @@ -16,7 +15,9 @@ import { map } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { isValidDate } from '../../shared/date.util'; -import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; +import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; +import { RemoteData } from '../../core/data/remote-data'; +import { Item } from '../../core/shared/item.model'; @Component({ selector: 'ds-browse-by-date-page', @@ -72,30 +73,24 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { /** * Update the StartsWith options - * In this implementation, it creates a list of years starting from now, going all the way back to the earliest - * date found on an item within this scope. The further back in time, the bigger the change in years become to avoid - * extremely long lists with a one-year difference. + * In this implementation, it creates a list of years starting from the most recent item or the current year, going + * all the way back to the earliest date found on an item within this scope. The further back in time, the bigger + * the change in years become to avoid extremely long lists with a one-year difference. * To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this. * @param definition The metadata definition to fetch the first item for * @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field) * @param scope The scope under which to fetch the earliest item for */ updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) { + const firstItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC); + const lastItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC); this.subs.push( - this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData) => { - let lowerLimit = this.appConfig.browseBy.defaultLowerLimit; - if (hasValue(firstItemRD.payload)) { - const date = firstItemRD.payload.firstMetadataValue(metadataKeys); - if (isNotEmpty(date) && isValidDate(date)) { - const dateObj = new Date(date); - // TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC. - lowerLimit = isNaN(dateObj.getUTCFullYear()) ? lowerLimit : dateObj.getUTCFullYear(); - } - } + observableCombineLatest([firstItemRD, lastItemRD]).subscribe(([firstItem, lastItem]) => { + let lowerLimit = this.getLimit(firstItem, metadataKeys, this.appConfig.browseBy.defaultLowerLimit); + let upperLimit = this.getLimit(lastItem, metadataKeys, new Date().getUTCFullYear()); const options = []; - const currentYear = new Date().getUTCFullYear(); - const oneYearBreak = Math.floor((currentYear - this.appConfig.browseBy.oneYearLimit) / 5) * 5; - const fiveYearBreak = Math.floor((currentYear - this.appConfig.browseBy.fiveYearLimit) / 10) * 10; + const oneYearBreak = Math.floor((upperLimit - this.appConfig.browseBy.oneYearLimit) / 5) * 5; + const fiveYearBreak = Math.floor((upperLimit - this.appConfig.browseBy.fiveYearLimit) / 10) * 10; if (lowerLimit <= fiveYearBreak) { lowerLimit -= 10; } else if (lowerLimit <= oneYearBreak) { @@ -103,7 +98,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { } else { lowerLimit -= 1; } - let i = currentYear; + let i = upperLimit; while (i > lowerLimit) { options.push(i); if (i <= fiveYearBreak) { @@ -121,4 +116,24 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { }) ); } + + /** + * Returns the year from the item metadata field or the limit. + * @param itemRD the item remote data + * @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field) + * @param limit the limit to use if the year can't be found in metadata + * @private + */ + private getLimit(itemRD: RemoteData, metadataKeys: string[], limit: number): number { + if (hasValue(itemRD.payload)) { + const date = itemRD.payload.firstMetadataValue(metadataKeys); + if (isNotEmpty(date) && isValidDate(date)) { + const dateObj = new Date(date); + // TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC. + return isNaN(dateObj.getUTCFullYear()) ? limit : dateObj.getUTCFullYear(); + } else { + return new Date().getUTCFullYear(); + } + } + } } diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts index be28015069..989213a978 100644 --- a/src/app/core/browse/browse.service.ts +++ b/src/app/core/browse/browse.service.ts @@ -22,6 +22,7 @@ import { BrowseEntrySearchOptions } from './browse-entry-search-options.model'; import { HrefOnlyDataService } from '../data/href-only-data.service'; import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { BrowseDefinitionDataService } from './browse-definition-data.service'; +import { SortDirection } from '../cache/models/sort-options.model'; export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig[] = [ @@ -160,8 +161,9 @@ export class BrowseService { * Get the first item for a metadata definition in an optional scope * @param definition * @param scope + * @param sortDirection optional sort parameter */ - getFirstItemFor(definition: string, scope?: string): Observable> { + getFirstItemFor(definition: string, scope?: string, sortDirection?: SortDirection): Observable> { const href$ = this.getBrowseDefinitions().pipe( getBrowseDefinitionLinks(definition), hasValueOperator(), @@ -177,6 +179,9 @@ export class BrowseService { } args.push('page=0'); args.push('size=1'); + if (sortDirection) { + args.push('sort=default,' + sortDirection); + } if (isNotEmpty(args)) { href = new URLCombiner(href, `?${args.join('&')}`).toString(); } From 24cc3fb76e11e936f9aac632093052e66190d21a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 29 Mar 2023 12:53:59 -0700 Subject: [PATCH 30/33] reverted browse-by-metadata change --- .../browse-by-metadata-page.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 60db6c3148..12264e9158 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -152,9 +152,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { this.browseId = params.id || this.defaultBrowseId; this.authority = params.authority; - if (typeof params.value === 'string'){ - this.value = params.value.trim(); - } + this.value = +params.value || params.value || ''; if (typeof params.startsWith === 'string'){ this.startsWith = params.startsWith.trim(); From 521b7d4db83a4de48f3f723cf48c3c491188d9d7 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 30 Mar 2023 11:06:38 -0700 Subject: [PATCH 31/33] updated value param check --- .../browse-by-metadata-page.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 12264e9158..3e8a6db816 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -152,7 +152,11 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { this.browseId = params.id || this.defaultBrowseId; this.authority = params.authority; - this.value = +params.value || params.value || ''; + if (typeof params.value === 'string'){ + this.value = params.value.trim(); + } else { + this.value = ''; + } if (typeof params.startsWith === 'string'){ this.startsWith = params.startsWith.trim(); From fb45f5f80753a70820997b5184eca0ffb4973cc2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 4 Apr 2023 14:07:22 -0500 Subject: [PATCH 32/33] Remove flakey check for success alert box. Unnecessary for this test, and sometimes randomly fails (if alert box closes before it can be clicked closed) --- cypress/e2e/submission.cy.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/cypress/e2e/submission.cy.ts b/cypress/e2e/submission.cy.ts index 8262bc841e..ed10b2d13a 100644 --- a/cypress/e2e/submission.cy.ts +++ b/cypress/e2e/submission.cy.ts @@ -122,8 +122,6 @@ describe('New Submission page', () => { // Wait for upload to complete before proceeding cy.wait('@upload'); - // Close the upload success notice - cy.get('[data-dismiss="alert"]').click({multiple: true}); // Wait for deposit button to not be disabled & click it. cy.get('button#deposit').should('not.be.disabled').click(); From ff5ccf30eebbc21b1e319a9dfc53b08eec509cd0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 4 Apr 2023 14:08:34 -0500 Subject: [PATCH 33/33] Minor update to latest Cypress --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b994672548..aceda4090b 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "compression-webpack-plugin": "^9.2.0", "copy-webpack-plugin": "^6.4.1", "cross-env": "^7.0.3", - "cypress": "^12.0.1", + "cypress": "12.9.0", "cypress-axe": "^1.1.0", "deep-freeze": "0.0.1", "eslint": "^8.2.0", diff --git a/yarn.lock b/yarn.lock index e67322f016..abb5e4c4d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4270,10 +4270,10 @@ cypress-axe@^1.1.0: resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.4.0.tgz#e67482bfe9e740796bf77c7823f19781a8a2faff" integrity sha512-Ut7NKfzjyKm0BEbt2WxuKtLkIXmx6FD2j0RwdvO/Ykl7GmB/qRQkwbKLk3VP35+83hiIr8GKD04PDdrTK5BnyA== -cypress@^12.0.1: - version "12.7.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.7.0.tgz#69900f82af76cf3ba0ddb9b59ec3b0d38222ab22" - integrity sha512-7rq+nmhzz0u6yabCFyPtADU2OOrYt6pvUau9qV7xyifJ/hnsaw/vkr0tnLlcuuQKUAOC1v1M1e4Z0zG7S0IAvA== +cypress@12.9.0: + version "12.9.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.9.0.tgz#e6ab43cf329fd7c821ef7645517649d72ccf0a12" + integrity sha512-Ofe09LbHKgSqX89Iy1xen2WvpgbvNxDzsWx3mgU1mfILouELeXYGwIib3ItCwoRrRifoQwcBFmY54Vs0zw7QCg== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4"