Merge branch 'main' into task/main/CST-15593

# Conflicts:
#	package-lock.json
#	package.json
#	src/assets/i18n/ar.json5
This commit is contained in:
Andrea Barbasso
2024-10-24 10:12:18 +02:00
64 changed files with 15322 additions and 6064 deletions

294
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,294 @@
#-------------------
# DSpace's dependabot rules. Enables npm updates for all dependencies on a weekly basis
# for main and any maintenance branches. Security updates only apply to main.
#-------------------
version: 2
updates:
###############
## Main branch
###############
# NOTE: At this time, "security-updates" rules only apply if "target-branch" is unspecified
# So, only this first section can include "applies-to: security-updates"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
# Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10
# Group together Angular package upgrades
groups:
# Group together all minor/patch version updates for Angular in a single PR
angular:
applies-to: version-updates
patterns:
- "@angular*"
update-types:
- "minor"
- "patch"
# Group together all security updates for Angular. Only accept minor/patch types.
angular-security:
applies-to: security-updates
patterns:
- "@angular*"
update-types:
- "minor"
- "patch"
# Group together all minor/patch version updates for NgRx in a single PR
ngrx:
applies-to: version-updates
patterns:
- "@ngrx*"
update-types:
- "minor"
- "patch"
# Group together all security updates for NgRx. Only accept minor/patch types.
ngrx-security:
applies-to: security-updates
patterns:
- "@ngrx*"
update-types:
- "minor"
- "patch"
# Group together all patch version updates for eslint in a single PR
eslint:
applies-to: version-updates
patterns:
- "@typescript-eslint*"
- "eslint*"
update-types:
- "minor"
- "patch"
# Group together all security updates for eslint.
eslint-security:
applies-to: security-updates
patterns:
- "@typescript-eslint*"
- "eslint*"
update-types:
- "minor"
- "patch"
# Group together any testing related version updates
testing:
applies-to: version-updates
patterns:
- "@cypress*"
- "cypress*"
- "jasmine*"
- "karma*"
- "ng-mocks"
update-types:
- "minor"
- "patch"
# Group together any testing related security updates
testing-security:
applies-to: security-updates
patterns:
- "@cypress*"
- "cypress*"
- "jasmine*"
- "karma*"
- "ng-mocks"
update-types:
- "minor"
- "patch"
# Group together any postcss related version updates
postcss:
applies-to: version-updates
patterns:
- "postcss*"
update-types:
- "minor"
- "patch"
# Group together any postcss related security updates
postcss-security:
applies-to: security-updates
patterns:
- "postcss*"
update-types:
- "minor"
- "patch"
# Group together any sass related version updates
sass:
applies-to: version-updates
patterns:
- "sass*"
update-types:
- "minor"
- "patch"
# Group together any sass related security updates
sass-security:
applies-to: security-updates
patterns:
- "sass*"
update-types:
- "minor"
- "patch"
# Group together any webpack related version updates
webpack:
applies-to: version-updates
patterns:
- "webpack*"
update-types:
- "minor"
- "patch"
# Group together any webpack related seurity updates
webpack-security:
applies-to: security-updates
patterns:
- "webpack*"
update-types:
- "minor"
- "patch"
ignore:
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*"
update-types: ["version-update:semver-major"]
#####################
## dspace-8_x branch
#####################
- package-ecosystem: "npm"
directory: "/"
target-branch: dspace-8_x
schedule:
interval: "weekly"
# Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10
# Group together Angular package upgrades
groups:
# Group together all patch version updates for Angular in a single PR
angular:
applies-to: version-updates
patterns:
- "@angular*"
update-types:
- "minor"
- "patch"
# Group together all minor/patch version updates for NgRx in a single PR
ngrx:
applies-to: version-updates
patterns:
- "@ngrx*"
update-types:
- "minor"
- "patch"
# Group together all patch version updates for eslint in a single PR
eslint:
applies-to: version-updates
patterns:
- "@typescript-eslint*"
- "eslint*"
update-types:
- "minor"
- "patch"
# Group together any testing related version updates
testing:
applies-to: version-updates
patterns:
- "@cypress*"
- "cypress*"
- "jasmine*"
- "karma*"
- "ng-mocks"
update-types:
- "minor"
- "patch"
# Group together any postcss related version updates
postcss:
applies-to: version-updates
patterns:
- "postcss*"
update-types:
- "minor"
- "patch"
# Group together any sass related version updates
sass:
applies-to: version-updates
patterns:
- "sass*"
update-types:
- "minor"
- "patch"
# Group together any webpack related version updates
webpack:
applies-to: version-updates
patterns:
- "webpack*"
update-types:
- "minor"
- "patch"
ignore:
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*"
update-types: ["version-update:semver-major"]
#####################
## dspace-7_x branch
#####################
- package-ecosystem: "npm"
directory: "/"
target-branch: dspace-7_x
schedule:
interval: "weekly"
# Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10
# Group together Angular package upgrades
groups:
# Group together all minor/patch version updates for Angular in a single PR
angular:
applies-to: version-updates
patterns:
- "@angular*"
update-types:
- "minor"
- "patch"
# Group together all minor/patch version updates for NgRx in a single PR
ngrx:
applies-to: version-updates
patterns:
- "@ngrx*"
update-types:
- "minor"
- "patch"
# Group together all patch version updates for eslint in a single PR
eslint:
applies-to: version-updates
patterns:
- "@typescript-eslint*"
- "eslint*"
update-types:
- "minor"
- "patch"
# Group together any testing related version updates
testing:
applies-to: version-updates
patterns:
- "@cypress*"
- "cypress*"
- "jasmine*"
- "karma*"
- "ng-mocks"
update-types:
- "minor"
- "patch"
# Group together any postcss related version updates
postcss:
applies-to: version-updates
patterns:
- "postcss*"
update-types:
- "minor"
- "patch"
# Group together any sass related version updates
sass:
applies-to: version-updates
patterns:
- "sass*"
update-types:
- "minor"
- "patch"
ignore:
# 7.x Cannot update Webpack past v5.76.1 as later versions not supported by Angular 15
# See also https://github.com/DSpace/dspace-angular/pull/3283#issuecomment-2372488489
- dependency-name: "webpack"
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*"
update-types: ["version-update:semver-major"]

View File

@@ -30,7 +30,6 @@
"lodash",
"jwt-decode",
"uuid",
"webfontloader",
"zone.js"
],
"outputPath": "dist/browser",

View File

@@ -1,6 +1,7 @@
import { defineConfig } from 'cypress';
export default defineConfig({
video: true,
videosFolder: 'cypress/videos',
screenshotsFolder: 'cypress/screenshots',
fixturesFolder: 'cypress/fixtures',
@@ -18,6 +19,7 @@ export default defineConfig({
// Admin account used for administrative tests
DSPACE_TEST_ADMIN_USER: 'dspacedemo+admin@gmail.com',
DSPACE_TEST_ADMIN_USER_UUID: '335647b6-8a52-4ecb-a8c1-7ebabb199bda',
DSPACE_TEST_ADMIN_PASSWORD: 'dspace',
// Community/collection/publication used for view/edit tests
DSPACE_TEST_COMMUNITY: '0958c910-2037-42a9-81c7-dca80e3892b4',
@@ -33,6 +35,8 @@ export default defineConfig({
// Account used to test basic submission process
DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com',
DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace',
// Administrator users group
DSPACE_ADMINISTRATOR_GROUP: 'e59f5659-bff9-451e-b28f-439e7bd467e4'
},
e2e: {
// Setup our plugins for e2e tests

View File

@@ -0,0 +1,48 @@
import { testA11y } from 'cypress/support/utils';
describe('Admin Add New Modals', () => {
beforeEach(() => {
// Must login as an Admin for sidebar to appear
cy.visit('/login');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('Add new Community modal should pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on entry of menu
cy.get('#admin-menu-section-new-title').click();
cy.get('a[data-test="menu.section.new_community"]').click();
// Analyze <ds-create-community-parent-selector> for accessibility
testA11y('ds-create-community-parent-selector');
});
it('Add new Collection modal should pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on entry of menu
cy.get('#admin-menu-section-new-title').click();
cy.get('a[data-test="menu.section.new_collection"]').click();
// Analyze <ds-create-collection-parent-selector> for accessibility
testA11y('ds-create-collection-parent-selector');
});
it('Add new Item modal should pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on entry of menu
cy.get('#admin-menu-section-new-title').click();
cy.get('a[data-test="menu.section.new_item"]').click();
// Analyze <ds-create-item-parent-selector> for accessibility
testA11y('ds-create-item-parent-selector');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Admin Curation Tasks', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/curation-tasks');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Page must first be visible
cy.get('ds-admin-curation-task').should('be.visible');
// Analyze <ds-admin-curation-task> for accessibility issues
testA11y('ds-admin-curation-task');
});
});

View File

@@ -0,0 +1,48 @@
import { testA11y } from 'cypress/support/utils';
describe('Admin Edit Modals', () => {
beforeEach(() => {
// Must login as an Admin for sidebar to appear
cy.visit('/login');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('Edit Community modal should pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on entry of menu
cy.get('#admin-menu-section-edit-title').click();
cy.get('a[data-test="menu.section.edit_community"]').click();
// Analyze <ds-edit-community-selector> for accessibility
testA11y('ds-edit-community-selector');
});
it('Edit Collection modal should pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on entry of menu
cy.get('#admin-menu-section-edit-title').click();
cy.get('a[data-test="menu.section.edit_collection"]').click();
// Analyze <ds-edit-collection-selector> for accessibility
testA11y('ds-edit-collection-selector');
});
it('Edit Item modal should pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on entry of menu
cy.get('#admin-menu-section-edit-title').click();
cy.get('a[data-test="menu.section.edit_item"]').click();
// Analyze <ds-edit-item-selector> for accessibility
testA11y('ds-edit-item-selector');
});
});

View File

@@ -0,0 +1,35 @@
import { testA11y } from 'cypress/support/utils';
describe('Admin Export Modals', () => {
beforeEach(() => {
// Must login as an Admin for sidebar to appear
cy.visit('/login');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('Export metadata modal should pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on entry of menu
cy.get('#admin-menu-section-export-title').click();
cy.get('a[data-test="menu.section.export_metadata"]').click();
// Analyze <ds-export-metadata-selector> for accessibility
testA11y('ds-export-metadata-selector');
});
it('Export batch modal should pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on entry of menu
cy.get('#admin-menu-section-export-title').click();
cy.get('a[data-test="menu.section.export_batch"]').click();
// Analyze <ds-export-batch-selector> for accessibility
testA11y('ds-export-batch-selector');
});
});

View File

@@ -0,0 +1,17 @@
import { testA11y } from 'cypress/support/utils';
describe('Admin Notifications Publication Claim Page', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/notifications/publication-claim');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
//Page must first be visible
cy.get('ds-admin-notifications-publication-claim-page').should('be.visible');
// Analyze <ds-admin-notifications-publication-claim-page> for accessibility issues
testA11y('ds-admin-notifications-publication-claim-page');
});
});

View File

@@ -0,0 +1,21 @@
import { testA11y } from 'cypress/support/utils';
describe('Admin Search Page', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/search');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
//Page must first be visible
cy.get('ds-admin-search-page').should('be.visible');
// At least one search result should be displayed
cy.get('[data-test="list-object"]').should('be.visible');
// Click each filter toggle to open *every* filter
// (As we want to scan filter section for accessibility issues as well)
cy.get('[data-test="filter-toggle"]').click({ multiple: true });
// Analyze <ds-admin-search-page> for accessibility issues
testA11y('ds-admin-search-page');
});
});

View File

@@ -0,0 +1,21 @@
import { testA11y } from 'cypress/support/utils';
describe('Admin Workflow Page', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/workflow');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Page must first be visible
cy.get('ds-admin-workflow-page').should('be.visible');
// At least one search result should be displayed
cy.get('[data-test="list-object"]').should('be.visible');
// Click each filter toggle to open *every* filter
// (As we want to scan filter section for accessibility issues as well)
cy.get('[data-test="filter-toggle"]').click({ multiple: true });
// Analyze <ds-admin-workflow-page> for accessibility issues
testA11y('ds-admin-workflow-page');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Batch Import Page', () => {
beforeEach(() => {
// Must login as an Admin to see processes
cy.visit('/admin/batch-import');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Batch import form must first be visible
cy.get('ds-batch-import-page').should('be.visible');
// Analyze <ds-batch-import-page> for accessibility issues
testA11y('ds-batch-import-page');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Bitstreams Formats', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/registries/bitstream-formats');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Page must first be visible
cy.get('ds-bitstream-formats').should('be.visible');
// Analyze <ds-bitstream-formats> for accessibility issues
testA11y('ds-bitstream-formats');
});
});

View File

@@ -0,0 +1,31 @@
import { testA11y } from 'cypress/support/utils';
import { Options } from 'cypress-axe';
describe('Bulk Access', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/access-control/bulk-access');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Page must first be visible
cy.get('ds-bulk-access').should('be.visible');
// At least one search result should be displayed
cy.get('[data-test="list-object"]').should('be.visible');
// Click each filter toggle to open *every* filter
// (As we want to scan filter section for accessibility issues as well)
cy.get('[data-test="filter-toggle"]').click({ multiple: true });
// Analyze <ds-bulk-access> for accessibility issues
testA11y('ds-bulk-access', {
rules: {
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
'aria-required-children': { enabled: false },
'nested-interactive': { enabled: false },
// Card titles fail this test currently
'heading-order': { enabled: false },
},
} as Options);
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Create Eperson', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/access-control/epeople/create');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Form must first be visible
cy.get('ds-eperson-form').should('be.visible');
// Analyze <ds-eperson-form> for accessibility issues
testA11y('ds-eperson-form');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Create Group', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/access-control/groups/create');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Form must first be visible
cy.get('ds-group-form').should('be.visible');
// Analyze <ds-group-form> for accessibility issues
testA11y('ds-group-form');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Edit Eperson', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/access-control/epeople/'.concat(Cypress.env('DSPACE_TEST_ADMIN_USER_UUID')).concat('/edit'));
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Form must first be visible
cy.get('ds-eperson-form').should('be.visible');
// Analyze <ds-eperson-form> for accessibility issues
testA11y('ds-eperson-form');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Edit Group', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/access-control/groups/'.concat(Cypress.env('DSPACE_ADMINISTRATOR_GROUP')).concat('/edit'));
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Form must first be visible
cy.get('ds-group-form').should('be.visible');
// Analyze <ds-group-form> for accessibility issues
testA11y('ds-group-form');
});
});

View File

@@ -0,0 +1,13 @@
import { testA11y } from 'cypress/support/utils';
describe('End User Agreement', () => {
it('should pass accessibility tests', () => {
cy.visit('/info/end-user-agreement');
// Page must first be visible
cy.get('ds-end-user-agreement').should('be.visible');
// Analyze <ds-end-user-agreement> for accessibility
testA11y('ds-end-user-agreement');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Epeople registry', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/access-control/epeople');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Epeople registry page must first be visible
cy.get('ds-epeople-registry').should('be.visible');
// Analyze <ds-epeople-registry> for accessibility issues
testA11y('ds-epeople-registry');
});
});

View File

@@ -0,0 +1,13 @@
import { testA11y } from 'cypress/support/utils';
describe('Feedback', () => {
it('should pass accessibility tests', () => {
cy.visit('/info/feedback');
// Page must first be visible
cy.get('ds-feedback').should('be.visible');
// Analyze <ds-feedback> for accessibility
testA11y('ds-feedback');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Groups registry', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/access-control/groups');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Epeople registry page must first be visible
cy.get('ds-groups-registry').should('be.visible');
// Analyze <ds-groups-registry> for accessibility issues
testA11y('ds-groups-registry');
});
});

View File

@@ -10,4 +10,29 @@ describe('Header', () => {
// Analyze <ds-header> for accessibility
testA11y('ds-header');
});
it('should allow for changing language to German (for example)', () => {
cy.visit('/');
// Click the language switcher (globe) in header
cy.get('a[data-test="lang-switch"]').click();
// Click on the "Deusch" language in dropdown
cy.get('#language-menu-list li').contains('Deutsch').click();
// HTML "lang" attribute should switch to "de"
cy.get('html').invoke('attr', 'lang').should('eq', 'de');
// Login menu should now be in German
cy.get('a[data-test="login-menu"]').contains('Anmelden');
// Change back to English from language switcher
cy.get('a[data-test="lang-switch"]').click();
cy.get('#language-menu-list li').contains('English').click();
// HTML "lang" attribute should switch to "en"
cy.get('html').invoke('attr', 'lang').should('eq', 'en');
// Login menu should now be in English
cy.get('a[data-test="login-menu"]').contains('Log In');
});
});

View File

@@ -0,0 +1,62 @@
import { testA11y } from 'cypress/support/utils';
import { Options } from 'cypress-axe';
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/health');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
describe('Health Page > Status Tab', () => {
it('should pass accessibility tests', () => {
cy.intercept('GET', '/server/actuator/health').as('status');
cy.wait('@status');
cy.get('a[data-test="health-page.status-tab"]').click();
// Page must first be visible
cy.get('ds-health-page').should('be.visible');
cy.get('ds-health-panel').should('be.visible');
// wait for all the ds-health-info-component components to be rendered
cy.get('div[role="tabpanel"]').each(($panel: HTMLDivElement) => {
cy.wrap($panel).find('ds-health-component').should('be.visible');
});
// Analyze <ds-health-page> for accessibility issues
testA11y('ds-health-page', {
rules: {
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
'aria-required-children': { enabled: false },
'nested-interactive': { enabled: false },
},
} as Options);
});
});
describe('Health Page > Info Tab', () => {
it('should pass accessibility tests', () => {
cy.intercept('GET', '/server/actuator/info').as('info');
cy.wait('@info');
cy.get('a[data-test="health-page.info-tab"]').click();
// Page must first be visible
cy.get('ds-health-page').should('be.visible');
cy.get('ds-health-info').should('be.visible');
// wait for all the ds-health-info-component components to be rendered
cy.get('div[role="tabpanel"]').each(($panel: HTMLDivElement) => {
cy.wrap($panel).find('ds-health-info-component').should('be.visible');
});
// Analyze <ds-health-info> for accessibility issues
testA11y('ds-health-info', {
rules: {
// All panels are accordions & fail "aria-required-children" and "nested-interactive".
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
'aria-required-children': { enabled: false },
'nested-interactive': { enabled: false },
},
} as Options);
});
});

View File

@@ -9,12 +9,20 @@ beforeEach(() => {
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
// We need to wait for the correction types allowed for the item to be loaded to be sure that each tab is fully loaded.
// This because the edit item page causes often tests to fails due to timeout.
cy.intercept('GET', 'server/api/config/correctiontypes/search/findByItem*').as('correctionTypes');
cy.wait('@correctionTypes');
});
describe('Edit Item > Edit Metadata tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="metadata"]').click();
// Our selected tab should be active
cy.get('a[data-test="metadata"]').should('have.class', 'active');
// <ds-edit-item-page> tag must be loaded
cy.get('ds-edit-item-page').should('be.visible');
@@ -33,6 +41,9 @@ describe('Edit Item > Status tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="status"]').click();
// Our selected tab should be active
cy.get('a[data-test="status"]').should('have.class', 'active');
// <ds-item-status> tag must be loaded
cy.get('ds-item-status').should('be.visible');
@@ -46,6 +57,9 @@ describe('Edit Item > Bitstreams tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="bitstreams"]').click();
// Our selected tab should be active
cy.get('a[data-test="bitstreams"]').should('have.class', 'active');
// <ds-item-bitstreams> tag must be loaded
cy.get('ds-item-bitstreams').should('be.visible');
@@ -70,6 +84,9 @@ describe('Edit Item > Curate tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="curate"]').click();
// Our selected tab should be active
cy.get('a[data-test="curate"]').should('have.class', 'active');
// <ds-item-curate> tag must be loaded
cy.get('ds-item-curate').should('be.visible');
@@ -83,6 +100,9 @@ describe('Edit Item > Relationships tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="relationships"]').click();
// Our selected tab should be active
cy.get('a[data-test="relationships"]').should('have.class', 'active');
// <ds-item-relationships> tag must be loaded
cy.get('ds-item-relationships').should('be.visible');
@@ -96,6 +116,9 @@ describe('Edit Item > Version History tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="versionhistory"]').click();
// Our selected tab should be active
cy.get('a[data-test="versionhistory"]').should('have.class', 'active');
// <ds-item-version-history> tag must be loaded
cy.get('ds-item-version-history').should('be.visible');
@@ -109,6 +132,9 @@ describe('Edit Item > Access Control tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="access-control"]').click();
// Our selected tab should be active
cy.get('a[data-test="access-control"]').should('have.class', 'active');
// <ds-item-access-control> tag must be loaded
cy.get('ds-item-access-control').should('be.visible');
@@ -122,6 +148,9 @@ describe('Edit Item > Collection Mapper tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="mapper"]').click();
// Our selected tab should be active
cy.get('a[data-test="mapper"]').should('have.class', 'active');
// <ds-item-collection-mapper> tag must be loaded
cy.get('ds-item-collection-mapper').should('be.visible');

View File

@@ -3,31 +3,31 @@ import { testA11y } from 'cypress/support/utils';
const page = {
openLoginMenu() {
// Click the "Log In" dropdown menu in header
cy.get('ds-header [data-test="login-menu"]').click();
cy.get('[data-test="login-menu"]').click();
},
openUserMenu() {
// Once logged in, click the User menu in header
cy.get('ds-header [data-test="user-menu"]').click();
cy.get('[data-test="user-menu"]').click();
},
submitLoginAndPasswordByPressingButton(email, password) {
// Enter email
cy.get('ds-header [data-test="email"]').type(email);
cy.get('[data-test="email"]').type(email);
// Enter password
cy.get('ds-header [data-test="password"]').type(password);
cy.get('[data-test="password"]').type(password);
// Click login button
cy.get('ds-header [data-test="login-button"]').click();
cy.get('[data-test="login-button"]').click();
},
submitLoginAndPasswordByPressingEnter(email, password) {
// In opened Login modal, fill out email & password, then click Enter
cy.get('ds-header [data-test="email"]').type(email);
cy.get('ds-header [data-test="password"]').type(password);
cy.get('ds-header [data-test="password"]').type('{enter}');
cy.get('[data-test="email"]').type(email);
cy.get('[data-test="password"]').type(password);
cy.get('[data-test="password"]').type('{enter}');
},
submitLogoutByPressingButton() {
// This is the POST command that will actually log us out
cy.intercept('POST', '/server/api/authn/logout').as('logout');
// Click logout button
cy.get('ds-header [data-test="logout-button"]').click();
cy.get('[data-test="logout-button"]').click();
// Wait until above POST command responds before continuing
// (This ensures next action waits until logout completes)
cy.wait('@logout');
@@ -67,7 +67,7 @@ describe('Login Modal', () => {
// Login, and the <ds-log-in> tag should no longer exist
page.submitLoginAndPasswordByPressingEnter(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
cy.get('.form-login').should('not.exist');
cy.get('ds-log-in').should('not.exist');
// Verify we are still on homepage
cy.url().should('include', '/home');

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Metadata Import Page', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/metadata-import');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Metadata import form must first be visible
cy.get('ds-metadata-import-page').should('be.visible');
// Analyze <ds-metadata-import-page> for accessibility issues
testA11y('ds-metadata-import-page');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Metadata Registry', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/registries/metadata');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Page must first be visible
cy.get('ds-metadata-registry').should('be.visible');
// Analyze <ds-metadata-registry> for accessibility issues
testA11y('ds-metadata-registry');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Metadata Schema', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/registries/metadata/dc');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Page must first be visible
cy.get('ds-metadata-schema').should('be.visible');
// Analyze <ds-metadata-schema> for accessibility issues
testA11y('ds-metadata-schema');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('New Process', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/processes/new');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Process form must first be visible
cy.get('ds-new-process').should('be.visible');
// Analyze <ds-new-process> for accessibility issues
testA11y('ds-new-process');
});
});

13
cypress/e2e/privacy.cy.ts Normal file
View File

@@ -0,0 +1,13 @@
import { testA11y } from 'cypress/support/utils';
describe('Privacy', () => {
it('should pass accessibility tests', () => {
cy.visit('/info/privacy');
// Page must first be visible
cy.get('ds-privacy').should('be.visible');
// Analyze <ds-privacy> for accessibility
testA11y('ds-privacy');
});
});

View File

@@ -0,0 +1,17 @@
import { testA11y } from 'cypress/support/utils';
describe('Processes Overview', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/processes');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Process overview must first be visible
cy.get('ds-process-overview').should('be.visible');
// Analyze <ds-process-overview> for accessibility issues
testA11y('ds-process-overview');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Profile page', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/profile');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Process form must first be visible
cy.get('ds-profile-page').should('be.visible');
// Analyze <ds-profile-page> for accessibility issues
testA11y('ds-profile-page');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('Quality Assurance Source Page', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/notifications/quality-assurance');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Source page must first be visible
cy.get('ds-quality-assurance-source-page-component').should('be.visible');
// Analyze <ds-quality-assurance-source-page-component> for accessibility issues
testA11y('ds-quality-assurance-source-page-component');
});
});

View File

@@ -0,0 +1,16 @@
import { testA11y } from 'cypress/support/utils';
describe('System Wide Alert', () => {
beforeEach(() => {
// Must login as an Admin to see the page
cy.visit('/admin/system-wide-alert');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should pass accessibility tests', () => {
// Page must first be visible
cy.get('ds-system-wide-alert-form').should('be.visible');
// Analyze <ds-system-wide-alert-form> for accessibility issues
testA11y('ds-system-wide-alert-form');
});
});

View File

@@ -101,11 +101,11 @@ Cypress.Commands.add('login', login);
*/
function loginViaForm(email: string, password: string): void {
// Enter email
cy.get('ds-log-in [data-test="email"]').type(email);
cy.get('[data-test="email"]').type(email);
// Enter password
cy.get('ds-log-in [data-test="password"]').type(password);
cy.get('[data-test="password"]').type(password);
// Click login button
cy.get('ds-log-in [data-test="login-button"]').click();
cy.get('[data-test="login-button"]').click();
}
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
Cypress.Commands.add('loginViaForm', loginViaForm);

4657
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -77,7 +77,7 @@
},
"@ngtools/webpack": {
"@angular/compiler-cli": "^17.3.11",
"typescript": "~5.3.3"
"typescript": "~5.4.5"
},
"@nicky-lenaers/ngx-scroll-to": {
"@angular/common": "^17.3.11",
@@ -96,19 +96,19 @@
}
},
"dependencies": {
"@angular/animations": "^17.3.11",
"@angular/animations": "^17.3.12",
"@angular/cdk": "^17.3.10",
"@angular/common": "^17.3.11",
"@angular/compiler": "^17.3.11",
"@angular/core": "^17.3.11",
"@angular/forms": "^17.3.11",
"@angular/localize": "^17.3.11",
"@angular/platform-browser": "^17.3.11",
"@angular/platform-browser-dynamic": "^17.3.11",
"@angular/platform-server": "^17.3.11",
"@angular/router": "^17.3.11",
"@angular/ssr": "^17.3.8",
"@babel/runtime": "7.21.0",
"@angular/common": "^17.3.12",
"@angular/compiler": "^17.3.12",
"@angular/core": "^17.3.12",
"@angular/forms": "^17.3.12",
"@angular/localize": "^17.3.12",
"@angular/platform-browser": "^17.3.12",
"@angular/platform-browser-dynamic": "^17.3.12",
"@angular/platform-server": "^17.3.12",
"@angular/router": "^17.3.12",
"@angular/ssr": "^17.3.10",
"@babel/runtime": "7.25.9",
"@kolkov/ngx-gallery": "^2.0.1",
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
"@ng-dynamic-forms/core": "^16.0.0",
@@ -127,13 +127,13 @@
"cli-progress": "^3.12.0",
"colors": "^1.4.0",
"compression": "^1.7.4",
"cookie-parser": "1.4.6",
"core-js": "^3.30.1",
"cookie-parser": "1.4.7",
"core-js": "^3.38.1",
"date-fns": "^2.29.3",
"date-fns-tz": "^1.3.7",
"deepmerge": "^4.3.1",
"ejs": "^3.1.10",
"express": "^4.20.0",
"express": "^4.21.1",
"express-rate-limit": "^5.1.3",
"fast-json-patch": "^3.1.1",
"filesize": "^6.1.0",
@@ -150,9 +150,8 @@
"markdown-it": "^13.0.1",
"mirador": "^3.3.0",
"mirador-dl-plugin": "^0.13.0",
"mirador-share-plugin": "^0.11.0",
"mirador-share-plugin": "^0.16.0",
"morgan": "^1.10.0",
"ng-mocks": "^14.10.0",
"ng2-file-upload": "5.0.0",
"ng2-nouislider": "^2.0.0",
"ngx-infinite-scroll": "^16.0.0",
@@ -160,56 +159,53 @@
"ngx-ui-switch": "^14.1.0",
"nouislider": "^15.7.1",
"orejime": "^2.3.0",
"pem": "1.14.7",
"reflect-metadata": "^0.1.13",
"pem": "1.14.8",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.0",
"sanitize-html": "^2.12.1",
"sortablejs": "1.15.0",
"uuid": "^8.3.2",
"webfontloader": "1.6.28",
"zone.js": "~0.14.4"
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-builders/custom-webpack": "~17.0.2",
"@angular-devkit/build-angular": "^17.3.8",
"@angular-eslint/builder": "17.2.1",
"@angular-eslint/bundled-angular-compiler": "17.2.1",
"@angular-eslint/eslint-plugin": "17.2.1",
"@angular-eslint/eslint-plugin-template": "17.2.1",
"@angular-eslint/schematics": "17.2.1",
"@angular-eslint/template-parser": "17.2.1",
"@angular/cli": "^17.3.8",
"@angular-eslint/builder": "^17.5.3",
"@angular-eslint/bundled-angular-compiler": "^17.5.3",
"@angular-eslint/eslint-plugin": "^17.5.3",
"@angular-eslint/eslint-plugin-template": "^17.5.3",
"@angular-eslint/schematics": "^17.5.3",
"@angular-eslint/template-parser": "^17.5.3",
"@angular-eslint/utils": "^17.5.3",
"@angular/cli": "^17.3.10",
"@angular/compiler-cli": "^17.3.11",
"@angular/language-service": "^17.3.11",
"@angular/language-service": "^17.3.12",
"@cypress/schematic": "^1.5.0",
"@fortawesome/fontawesome-free": "^6.4.0",
"@fortawesome/fontawesome-free": "^6.6.0",
"@ngrx/store-devtools": "^17.1.1",
"@ngtools/webpack": "^16.2.12",
"@types/deep-freeze": "0.1.2",
"@ngtools/webpack": "^16.2.16",
"@types/deep-freeze": "0.1.5",
"@types/ejs": "^3.1.2",
"@types/express": "^4.17.17",
"@types/jasmine": "~3.6.0",
"@types/js-cookie": "2.2.6",
"@types/lodash": "^4.14.194",
"@types/lodash": "^4.17.12",
"@types/node": "^14.14.9",
"@types/sanitize-html": "^2.9.0",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@typescript-eslint/rule-tester": "^7.2.0",
"@typescript-eslint/utils": "^7.2.0",
"axe-core": "^4.7.2",
"browser-sync": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@typescript-eslint/rule-tester": "^7.18.0",
"@typescript-eslint/utils": "^7.18.0",
"axe-core": "^4.10.0",
"browser-sync": "^3.0.3",
"compression-webpack-plugin": "^9.2.0",
"copy-webpack-plugin": "^6.4.1",
"cross-env": "^7.0.3",
"cypress": "12.17.4",
"cypress-axe": "^1.4.0",
"cypress": "^13.15.0",
"cypress-axe": "^1.5.0",
"deep-freeze": "0.0.1",
"eslint": "^8.39.0",
"eslint-plugin-deprecation": "^1.4.1",
"eslint-plugin-dspace-angular-html": "file:./lint/dist/src/rules/html",
"eslint-plugin-dspace-angular-ts": "file:./lint/dist/src/rules/ts",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-import-newlines": "^1.3.1",
"eslint-plugin-jsdoc": "^45.0.0",
"eslint-plugin-jsonc": "^2.6.0",
@@ -217,16 +213,17 @@
"eslint-plugin-rxjs": "^5.0.3",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^3.2.0",
"express-static-gzip": "^2.1.7",
"express-static-gzip": "^2.1.8",
"jasmine": "^3.8.0",
"jasmine-core": "^3.8.0",
"jasmine-marbles": "0.9.2",
"karma": "^6.4.2",
"karma": "^6.4.4",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"karma-mocha-reporter": "2.2.5",
"ng-mocks": "^14.13.1",
"ngx-mask": "14.2.4",
"nodemon": "^2.0.22",
"postcss": "^8.4",
@@ -239,12 +236,12 @@
"react-dom": "^16.14.0",
"rimraf": "^3.0.2",
"rxjs-spy": "^8.0.2",
"sass": "~1.62.0",
"sass": "~1.80.3",
"sass-loader": "^12.6.0",
"sass-resources-loader": "^2.2.5",
"ts-node": "^8.10.2",
"typescript": "~5.3.3",
"webpack": "5.94.0",
"typescript": "~5.4.5",
"webpack": "5.95.0",
"webpack-bundle-analyzer": "^4.8.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"

View File

@@ -163,7 +163,7 @@ export class Metadata {
const outputKeys: string[] = [];
for (const inputKey of inputKeys) {
if (inputKey.includes('*')) {
const inputKeyRegex = new RegExp('^' + inputKey.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
const inputKeyRegex = new RegExp('^' + inputKey.replace(/\\/g, '\\\\').replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
for (const mapKey of Object.keys(mdMap)) {
if (!outputKeys.includes(mapKey) && inputKeyRegex.test(mapKey)) {
outputKeys.push(mapKey);

View File

@@ -1,6 +1,6 @@
<div *ngIf="buttonVisible$ | async">
<a href="javascript:void(0);"
role="button"
role="menuitem"
(click)="onClick()"
[attr.aria-label]="'nav.context-help-toggle' | translate"
[title]="'nav.context-help-toggle' | translate"

View File

@@ -3,7 +3,7 @@
<div *ngIf="(healthResponse | async) && (healthInfoResponse | async)">
<ul ngbNav #nav="ngbNav" [activeId]="'status'" class="nav-tabs">
<li [ngbNavItem]="'status'" role="presentation">
<a ngbNavLink>{{'health-page.status-tab' | translate}}</a>
<a data-test="health-page.status-tab" ngbNavLink>{{'health-page.status-tab' | translate}}</a>
<ng-template ngbNavContent>
<div id="status">
<ds-health-panel [healthResponse]="(healthResponse | async)"></ds-health-panel>
@@ -11,7 +11,7 @@
</ng-template>
</li>
<li [ngbNavItem]="'info'" role="presentation">
<a ngbNavLink>{{'health-page.info-tab' | translate}}</a>
<a data-test="health-page.info-tab" ngbNavLink>{{'health-page.info-tab' | translate}}</a>
<ng-template ngbNavContent>
<div id="info">
<ds-health-info [healthInfoResponse]="(healthInfoResponse | async)"></ds-health-info>

View File

@@ -11,5 +11,5 @@
[hideGear]="true">
</ds-viewable-collection>
</div>
<ds-error *ngIf="communitiesRD?.hasFailed " message="{{'error.top-level-communites' | translate}}"></ds-error>
<ds-error *ngIf="communitiesRD?.hasFailed " message="{{'error.top-level-communities' | translate}}"></ds-error>
<ds-loading *ngIf="communitiesRD?.isLoading " message="{{'loading.top-level-communities' | translate}}"></ds-loading></ng-container>

View File

@@ -63,7 +63,7 @@
<p>We reserve the right, but not the obligation, to: (1) monitor the Site for violations of these Terms of Use; (2) take appropriate legal action against anyone who, in our sole discretion, violates the law or these Terms of Use, including without limitation, reporting such user to law enforcement authorities; (3) in our sole discretion and without limitation, refuse, restrict access to, limit the availability of, or disable (to the extent technologically feasible) any of your Contributions or any portion thereof; (4) in our sole discretion and without limitation, notice, or liability, to remove from the Site or otherwise disable all files and content that are excessive in size or are in any way burdensome to our systems; and (5) otherwise manage the Site in a manner designed to protect our rights and property and to facilitate the proper functioning of the Site.</p>
<h2>Privacy policy</h2>
<p>We care about data privacy and security. Please review our <a [routerLink]="['/info/privacy']" target="_blank" rel="noopener noreferrer">Privacy Policy</a>. By using the Site, you agree to be bound by our Privacy Policy, which is incorporated into these Terms of Use.</p>
<p>We care about data privacy and security. Please review our <a class="link-contrast" [routerLink]="['/info/privacy']" target="_blank" rel="noopener noreferrer">Privacy Policy</a>. By using the Site, you agree to be bound by our Privacy Policy, which is incorporated into these Terms of Use.</p>
<p>Please be advised the Site is hosted in {{ 'info.end-user-agreement.hosting-country' | translate }}. If you access the Site from any other region of the world with laws or other requirements governing personal data collection, use, or disclosure that differ from applicable laws in {{ 'info.end-user-agreement.hosting-country' | translate }}, then through your continued use of the Site, you are transferring your data to {{ 'info.end-user-agreement.hosting-country' | translate }}, and you agree to have your data transferred to and processed in {{ 'info.end-user-agreement.hosting-country' | translate }}.</p>
<h2>Term and termination</h2>
@@ -92,4 +92,4 @@
<h2>Miscellaneous</h2>
<p>These Terms of Use and any policies or operating rules posted by us on the Site or in respect to the Site constitute the entire agreement and understanding between you and us. Our failure to exercise or enforce any right or provision of these Terms of Use shall not operate as a waiver of such right or provision. These Terms of Use operate to the fullest extent permissible by law. We may assign any or all of our rights and obligations to others at any time. We shall not be responsible or liable for any loss, damage, delay, or failure to act caused by any cause beyond our reasonable control. If any provision or part of a provision of these Terms of Use is determined to be unlawful, void, or unenforceable, that provision or part of the provision is deemed severable from these Terms of Use and does not affect the validity and enforceability of any remaining provisions. There is no joint venture, partnership, employment or agency relationship created between you and us as a result of these Terms of Use or use of the Site. You agree that these Terms of Use will not be construed against us by virtue of having drafted them. You hereby waive any and all defenses you may have based on the electronic form of these Terms of Use and the lack of signing by the parties hereto to execute these Terms of Use.</p>
<p><a [routerLink]="'.'" fragment="ref-a" id="comment-a">[a]</a>&nbsp;The DSpace software used to run this site is open source. Options for reuse and reproduction of the DSpace software is governed by its open source license: <a href="https://github.com/DSpace/DSpace/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">https://github.com/DSpace/DSpace/blob/main/LICENSE</a></p>
<p><a class="link-contrast" [routerLink]="'.'" fragment="ref-a" id="comment-a">[a]</a>&nbsp;The DSpace software used to run this site is open source. Options for reuse and reproduction of the DSpace software is governed by its open source license: <a class="link-contrast" href="https://github.com/DSpace/DSpace/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">https://github.com/DSpace/DSpace/blob/main/LICENSE</a></p>

View File

@@ -83,7 +83,7 @@
<p>We may also disclose your personal information:</p>
<ul>
<li>To comply with any court order, law, or legal process, including to respond to any government or regulatory request.</li>
<li>To enforce or apply our <a [routerLink]="['/info/end-user-agreement']" target="_blank" rel="noopener noreferrer">End User Agreement</a> and other agreements, including for billing and collection purposes.</li>
<li>To enforce or apply our <a class="link-contrast" [routerLink]="['/info/end-user-agreement']" target="_blank" rel="noopener noreferrer">End User Agreement</a> and other agreements, including for billing and collection purposes.</li>
<li>If we believe disclosure is necessary or appropriate to protect the rights, property, or safety of the Company, our customers, or others.</li>
</ul>

View File

@@ -1,9 +1,9 @@
<div class="col-3 float-left d-flex h-100 action-label">
<span class="justify-content-center align-self-center">
<div class="col-12 col-md-3 h-auto float-left d-flex action-label">
<span class="justify-content-center align-self-center font-weight-bold">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.label' | translate}}
</span>
</div>
<div class="col-9 float-left action-button">
<div class="col-12 col-md-9 float-left action-button">
<span *ngIf="operation.authorized">
<button class="btn btn-outline-primary" [disabled]="operation.disabled" [routerLink]="operation.operationUrl" [attr.aria-label]="'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}

View File

@@ -1,28 +1,28 @@
<p class="mt-2">{{'item.edit.tabs.status.description' | translate}}</p>
<div class="row">
<div *ngFor="let statusKey of statusDataKeys" class="w-100">
<div class="col-3 float-left status-label">
<div *ngFor="let statusKey of statusDataKeys" class="w-100 pt-1">
<div class="col-12 col-md-3 float-left status-label font-weight-bold">
{{'item.edit.tabs.status.labels.' + statusKey | translate}}:
</div>
<div class="col-9 float-left status-data" id="status-{{statusKey}}">
<div class="col-12 col-md-9 float-left status-data" id="status-{{statusKey}}">
{{statusData[statusKey]}}
</div>
</div>
<div *ngFor="let identifier of (identifiers$ | async)" class="w-100">
<div *ngFor="let identifier of (identifiers$ | async)" class="w-100 pt-1">
<div *ngIf="(identifier.identifierType==='doi')">
<div class="col-3 float-left status-label">
<div class="col-12 col-md-3 float-left status-label font-weight-bold">
{{identifier.identifierType.toLocaleUpperCase()}}
</div>
<div class="col-9 float-left status-label">{{identifier.value}}
<div class="col-12 col-md-9 float-left status-label font-weight-bold">{{identifier.value}}
({{"item.edit.identifiers.doi.status."+identifier.identifierStatus|translate}})</div>
</div>
</div>
<div class="col-3 float-left status-label">
<div class="col-12 col-md-3 float-left status-label font-weight-bold">
{{'item.edit.tabs.status.labels.itemPage' | translate}}:
</div>
<div class="col-9 float-left status-data" id="status-itemPage">
<div class="col-12 col-md-9 float-left status-data" id="status-itemPage">
<a [routerLink]="itemPageRoute$ | async">{{itemPageRoute$ | async}}</a>
</div>

View File

@@ -7,7 +7,7 @@
</div>
<div class="row">
<div class="col-12">
<h4 class="border-bottom pb-2">{{'quality-assurance.source'| translate}}</h4>
<h3 class="h4 border-bottom pb-2">{{'quality-assurance.source'| translate}}</h3>
<ds-loading class="container" *ngIf="(isSourceLoading() | async)" message="{{'quality-assurance.loading' | translate}}"></ds-loading>
<ds-pagination *ngIf="(isSourceLoading() | async) !== true"

View File

@@ -44,7 +44,6 @@
<td>{{tableEntry.info}}</td>
<td>
<button [attr.aria-label]="'process.overview.delete-process' | translate"
aria-hidden="true"
(click)="processBulkDeleteService.toggleDelete(tableEntry.process.processId)"
class="btn btn-outline-danger">
<i class="fas fa-trash"></i>

View File

@@ -31,6 +31,7 @@
</button>
</ng-container>
<button *ngIf="loading"
title="{{'loading.default' | translate}}"
class="list-group-item list-group-item-action border-0 list-entry">
<ds-loading [showMessage]="false"></ds-loading>
</button>

View File

@@ -5,6 +5,7 @@
aria-haspopup="menu"
[title]="'nav.language' | translate"
(click)="$event.preventDefault()" data-toggle="dropdown" ngbDropdownToggle
data-test="lang-switch"
tabindex="0">
<i class="fas fa-globe-asia fa-lg fa-fw"></i>
</a>

View File

@@ -6,5 +6,6 @@
(click)="activate($event)"
(keyup.space)="activate($event)"
(keyup.enter)="activate($event)"
[attr.data-test]="item.text"
>{{item.text | translate}}</a>
<span *ngIf="item.disabled" class="nav-item nav-link disabled">{{item.text | translate}}</span>
<span *ngIf="item.disabled" [attr.data-test]="item.text" class="nav-item nav-link disabled">{{item.text | translate}}</span>

View File

@@ -39,7 +39,8 @@
<div *ngIf="shouldShowBottomPager | async">
<div *ngIf="showPaginator" class="pagination justify-content-center clearfix bottom">
<ngb-pagination [boundaryLinks]="paginationOptions.boundaryLinks"
<ngb-pagination [attr.aria-label]="('pagination-control.page-number-bar' | translate) + paginationOptions.id"
[boundaryLinks]="paginationOptions.boundaryLinks"
[collectionSize]="collectionSize"
[disabled]="paginationOptions.disabled"
[ellipses]="paginationOptions.ellipses"

View File

@@ -8,21 +8,20 @@ import {
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { environment } from '../../../environments/environment.test';
import { MathService } from '../../core/shared/math.service';
import { MockMathService } from '../../core/shared/math.service.spec';
import { MarkdownDirective } from './markdown.directive';
@Component({
template: `<div dsMarkdown="test"></div>`,
template: `<div [dsMarkdown]="'test<script>alert(1);</script>'"></div>`,
standalone: true,
imports: [ MarkdownDirective ],
})
class TestComponent {}
describe('MarkdownDirective', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
let divEl: DebugElement;
beforeEach(async () => {
await TestBed.configureTestingModule({
@@ -32,12 +31,61 @@ describe('MarkdownDirective', () => {
}).compileComponents();
spyOn(MarkdownDirective.prototype, 'render');
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
divEl = fixture.debugElement.query(By.css('div'));
});
it('should call render method', () => {
fixture.detectChanges();
expect(MarkdownDirective.prototype.render).toHaveBeenCalled();
});
});
describe('MarkdownDirective sanitization with markdown disabled', () => {
let fixture: ComponentFixture<TestComponent>;
let divEl: DebugElement;
// Disable markdown
environment.markdown.enabled = false;
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
{ provide: MathService, useClass: MockMathService },
],
}).compileComponents();
fixture = TestBed.createComponent(TestComponent);
divEl = fixture.debugElement.query(By.css('div'));
});
it('should sanitize the script element out of innerHTML (markdown disabled)',() => {
fixture.detectChanges();
divEl = fixture.debugElement.query(By.css('div'));
expect(divEl.nativeElement.innerHTML).toEqual('test');
});
});
describe('MarkdownDirective sanitization with markdown enabled', () => {
let fixture: ComponentFixture<TestComponent>;
let divEl: DebugElement;
// Enable markdown
environment.markdown.enabled = true;
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
{ provide: MathService, useClass: MockMathService },
],
}).compileComponents();
fixture = TestBed.createComponent(TestComponent);
divEl = fixture.debugElement.query(By.css('div'));
});
it('should sanitize the script element out of innerHTML (markdown enabled)',() => {
fixture.detectChanges();
divEl = fixture.debugElement.query(By.css('div'));
expect(divEl.nativeElement.innerHTML).toEqual('test');
});
});

View File

@@ -55,7 +55,7 @@ export class MarkdownDirective implements OnInit, OnDestroy {
async render(value: string, forcePreview = false): Promise<SafeHtml> {
if (isEmpty(value) || (!environment.markdown.enabled && !forcePreview)) {
this.el.innerHTML = value;
this.el.innerHTML = this.sanitizer.sanitize(SecurityContext.HTML, value);
return;
} else {
if (environment.markdown.mathjax) {

View File

@@ -1,4 +1,4 @@
<div class="thumbnail" [class.limit-width]="limitWidth" *ngVar="(isLoading$ | async) as isLoading">
<div class="thumbnail" [class.limit-width]="limitWidth">
<div *ngIf="isLoading" class="thumbnail-content outer">
<div class="inner">
<div class="centered">
@@ -6,7 +6,6 @@
</div>
</div>
</div>
<ng-container *ngVar="(src$ | async) as src">
<!-- don't use *ngIf="!isLoading" so the thumbnail can load in while the animation is playing -->
<img *ngIf="src !== null" class="thumbnail-content img-fluid" [ngClass]="{'d-none': isLoading}"
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()" (load)="successHandler()">
@@ -17,5 +16,4 @@
</div>
</div>
</div>
</ng-container>
</div>

View File

@@ -96,31 +96,31 @@ describe('ThumbnailComponent', () => {
describe('loading', () => {
it('should start out with isLoading$ true', () => {
expect(comp.isLoading$.getValue()).toBeTrue();
expect(comp.isLoading).toBeTrue();
});
it('should set isLoading$ to false once an image is successfully loaded', () => {
comp.setSrc('http://bit.stream');
fixture.debugElement.query(By.css('img.thumbnail-content')).triggerEventHandler('load', new Event('load'));
expect(comp.isLoading$.getValue()).toBeFalse();
expect(comp.isLoading).toBeFalse();
});
it('should set isLoading$ to false once the src is set to null', () => {
comp.setSrc(null);
expect(comp.isLoading$.getValue()).toBeFalse();
expect(comp.isLoading).toBeFalse();
});
it('should show a loading animation while isLoading$ is true', () => {
expect(de.query(By.css('ds-loading'))).toBeTruthy();
comp.isLoading$.next(false);
comp.isLoading = false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-loading'))).toBeFalsy();
});
describe('with a thumbnail image', () => {
beforeEach(() => {
comp.src$.next('https://bit.stream');
comp.src = 'https://bit.stream';
fixture.detectChanges();
});
@@ -129,7 +129,7 @@ describe('ThumbnailComponent', () => {
expect(img).toBeTruthy();
expect(img.classes['d-none']).toBeTrue();
comp.isLoading$.next(false);
comp.isLoading = false;
fixture.detectChanges();
img = fixture.debugElement.query(By.css('img.thumbnail-content'));
expect(img).toBeTruthy();
@@ -140,14 +140,14 @@ describe('ThumbnailComponent', () => {
describe('without a thumbnail image', () => {
beforeEach(() => {
comp.src$.next(null);
comp.src = null;
fixture.detectChanges();
});
it('should only show the HTML placeholder once done loading', () => {
expect(fixture.debugElement.query(By.css('div.thumbnail-placeholder'))).toBeFalsy();
comp.isLoading$.next(false);
comp.isLoading = false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('div.thumbnail-placeholder'))).toBeTruthy();
});
@@ -243,14 +243,14 @@ describe('ThumbnailComponent', () => {
describe('fallback', () => {
describe('if there is a default image', () => {
it('should display the default image', () => {
comp.src$.next('http://bit.stream');
comp.src = 'http://bit.stream';
comp.defaultImage = 'http://default.img';
comp.errorHandler();
expect(comp.src$.getValue()).toBe(comp.defaultImage);
expect(comp.src).toBe(comp.defaultImage);
});
it('should include the alt text', () => {
comp.src$.next('http://bit.stream');
comp.src = 'http://bit.stream';
comp.defaultImage = 'http://default.img';
comp.errorHandler();
@@ -262,10 +262,10 @@ describe('ThumbnailComponent', () => {
describe('if there is no default image', () => {
it('should display the HTML placeholder', () => {
comp.src$.next('http://default.img');
comp.src = 'http://default.img';
comp.defaultImage = null;
comp.errorHandler();
expect(comp.src$.getValue()).toBe(null);
expect(comp.src).toBe(null);
fixture.detectChanges();
const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement;
@@ -357,7 +357,7 @@ describe('ThumbnailComponent', () => {
it('should show the default image', () => {
comp.defaultImage = 'default/image.jpg';
comp.ngOnChanges({});
expect(comp.src$.getValue()).toBe('default/image.jpg');
expect(comp.src).toBe('default/image.jpg');
});
});
});

View File

@@ -6,10 +6,7 @@ import {
SimpleChanges,
} from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import {
BehaviorSubject,
of as observableOf,
} from 'rxjs';
import { of as observableOf } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { AuthService } from '../core/auth/auth.service';
@@ -53,7 +50,7 @@ export class ThumbnailComponent implements OnChanges {
/**
* The src attribute used in the template to render the image.
*/
src$ = new BehaviorSubject<string>(undefined);
src: string = undefined;
retriedWithToken = false;
@@ -76,7 +73,7 @@ export class ThumbnailComponent implements OnChanges {
* Whether the thumbnail is currently loading
* Start out as true to avoid flashing the alt text while a thumbnail is being loaded.
*/
isLoading$ = new BehaviorSubject(true);
isLoading = true;
constructor(
protected auth: AuthService,
@@ -129,7 +126,7 @@ export class ThumbnailComponent implements OnChanges {
* Otherwise, fall back to the default image or a HTML placeholder
*/
errorHandler() {
const src = this.src$.getValue();
const src = this.src;
const thumbnail = this.bitstream;
const thumbnailSrc = thumbnail?._links?.content?.href;
@@ -181,9 +178,9 @@ export class ThumbnailComponent implements OnChanges {
* @param src
*/
setSrc(src: string): void {
this.src$.next(src);
this.src = src;
if (src === null) {
this.isLoading$.next(false);
this.isLoading = false;
}
}
@@ -191,6 +188,6 @@ export class ThumbnailComponent implements OnChanges {
* Stop the loading animation once the thumbnail is successfully loaded
*/
successHandler() {
this.isLoading$.next(false);
this.isLoading = false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5232,6 +5232,12 @@
// "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Volume": "Import remote journal volume",
"submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Volume": "Zeitschriftenband importieren",
// "submission.sections.describe.relationship-lookup.external-source.import-button-title.none": "Import remote item",
"submission.sections.describe.relationship-lookup.external-source.import-button-title.none": "Item importieren",
// "submission.sections.describe.relationship-lookup.external-source.import-button-title.Publication": "Import remote publication",
"submission.sections.describe.relationship-lookup.external-source.import-button-title.Publication": "Metadaten der Publikation importieren",
// "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Import Remote Author",
"submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Importiere Autor:innen-Metadaten",

View File

@@ -1048,6 +1048,8 @@
"pagination.next.button.disabled.tooltip": "No more pages of results",
"pagination.page-number-bar": "Control bar for page navigation, relative to element with ID: ",
"browse.startsWith": ", starting with {{ startsWith }}",
"browse.startsWith.choose_start": "(Choose start)",

View File

@@ -2053,7 +2053,7 @@
"community.all-lists.head": "Sous-communautés et collections",
// "community.sub-collection-list.head": "Collections of this Community",
"community.sub-collection-list.head": "collections au sein de cette communauté",
"community.sub-collection-list.head": "Collections au sein de cette communauté",
// "community.sub-community-list.head": "Communities of this Community",
"community.sub-community-list.head": "Sous-communautés au sein de cette communauté",

File diff suppressed because it is too large Load Diff

View File

@@ -470,3 +470,11 @@ ngb-accordion {
.mt-ncs { margin-top: calc(var(--ds-content-spacing) * -1); }
.mb-ncs { margin-bottom: calc(var(--ds-content-spacing) * -1); }
.my-ncs { margin-top: calc(var(--ds-content-spacing) * -1); margin-bottom: calc(var(--ds-content-spacing) * -1); }
.link-contrast {
// Rules for accessibility to meet minimum contrast and have an identifiable link between other texts
color: darken($link-color, 5%);
// We use underline to discern link from text as we can't make color lighter on a white bg
text-decoration: underline;
}

View File

@@ -79,7 +79,6 @@ const SCSS_LOADERS = [
export const commonExports = {
plugins: [
// @ts-expect-error: EnvironmentPlugin constructor types are currently to strict see issue https://github.com/webpack/webpack/issues/18719
new EnvironmentPlugin({
languageHashes: getFileHashes(path.join(__dirname, '..', 'src', 'assets', 'i18n'), /.*\.json5/g),
}),

View File

@@ -6,7 +6,6 @@ import { commonExports } from './webpack.common';
module.exports = Object.assign({}, commonExports, {
plugins: [
...commonExports.plugins,
// @ts-expect-error: EnvironmentPlugin constructor types are currently to strict see issue https://github.com/webpack/webpack/issues/18719
new EnvironmentPlugin({
'process.env': {
NODE_ENV: 'production',