mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-15 14:03:06 +00:00
Merge branch 'main' of github.com:4science/dspace-angular into CST-4499
This commit is contained in:
15
cypress/integration/breadcrumbs.spec.ts
Normal file
15
cypress/integration/breadcrumbs.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
|
||||
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);
|
||||
|
||||
// Wait for breadcrumbs to be visible
|
||||
cy.get('ds-breadcrumbs').should('be.visible');
|
||||
|
||||
// Analyze <ds-breadcrumbs> for accessibility
|
||||
testA11y('ds-breadcrumbs');
|
||||
});
|
||||
});
|
13
cypress/integration/browse-by-author.spec.ts
Normal file
13
cypress/integration/browse-by-author.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Browse By Author', () => {
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/browse/author');
|
||||
|
||||
// Wait for <ds-browse-by-metadata-page> to be visible
|
||||
cy.get('ds-browse-by-metadata-page').should('be.visible');
|
||||
|
||||
// Analyze <ds-browse-by-metadata-page> for accessibility
|
||||
testA11y('ds-browse-by-metadata-page');
|
||||
});
|
||||
});
|
13
cypress/integration/browse-by-dateissued.spec.ts
Normal file
13
cypress/integration/browse-by-dateissued.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Browse By Date Issued', () => {
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/browse/dateissued');
|
||||
|
||||
// Wait for <ds-browse-by-date-page> to be visible
|
||||
cy.get('ds-browse-by-date-page').should('be.visible');
|
||||
|
||||
// Analyze <ds-browse-by-date-page> for accessibility
|
||||
testA11y('ds-browse-by-date-page');
|
||||
});
|
||||
});
|
13
cypress/integration/browse-by-subject.spec.ts
Normal file
13
cypress/integration/browse-by-subject.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Browse By Subject', () => {
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/browse/subject');
|
||||
|
||||
// Wait for <ds-browse-by-metadata-page> to be visible
|
||||
cy.get('ds-browse-by-metadata-page').should('be.visible');
|
||||
|
||||
// Analyze <ds-browse-by-metadata-page> for accessibility
|
||||
testA11y('ds-browse-by-metadata-page');
|
||||
});
|
||||
});
|
13
cypress/integration/browse-by-title.spec.ts
Normal file
13
cypress/integration/browse-by-title.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Browse By Title', () => {
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/browse/title');
|
||||
|
||||
// Wait for <ds-browse-by-title-page> to be visible
|
||||
cy.get('ds-browse-by-title-page').should('be.visible');
|
||||
|
||||
// Analyze <ds-browse-by-title-page> for accessibility
|
||||
testA11y('ds-browse-by-title-page');
|
||||
});
|
||||
});
|
15
cypress/integration/collection-page.spec.ts
Normal file
15
cypress/integration/collection-page.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { TEST_COLLECTION } from 'cypress/support';
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Collection Page', () => {
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/collections/' + TEST_COLLECTION);
|
||||
|
||||
// <ds-collection-page> tag must be loaded
|
||||
cy.get('ds-collection-page').should('exist');
|
||||
|
||||
// Analyze <ds-collection-page> for accessibility issues
|
||||
testA11y('ds-collection-page');
|
||||
});
|
||||
});
|
32
cypress/integration/collection-statistics.spec.ts
Normal file
32
cypress/integration/collection-statistics.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { TEST_COLLECTION } from 'cypress/support';
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Collection Statistics Page', () => {
|
||||
const COLLECTIONSTATISTICSPAGE = '/statistics/collections/' + TEST_COLLECTION;
|
||||
|
||||
it('should load if you click on "Statistics" from a Collection page', () => {
|
||||
cy.visit('/collections/' + 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('exist');
|
||||
});
|
||||
|
||||
it('should contain a "Total visits per month" section', () => {
|
||||
cy.visit(COLLECTIONSTATISTICSPAGE);
|
||||
cy.get('.' + TEST_COLLECTION + '_TotalVisitsPerMonth').should('exist');
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit(COLLECTIONSTATISTICSPAGE);
|
||||
|
||||
// <ds-collection-statistics-page> tag must be loaded
|
||||
cy.get('ds-collection-statistics-page').should('exist');
|
||||
|
||||
// Analyze <ds-collection-statistics-page> for accessibility issues
|
||||
testA11y('ds-collection-statistics-page');
|
||||
});
|
||||
});
|
25
cypress/integration/community-list.spec.ts
Normal file
25
cypress/integration/community-list.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Options } from 'cypress-axe';
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Community List Page', () => {
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/community-list');
|
||||
|
||||
// <ds-community-list-page> tag must be loaded
|
||||
cy.get('ds-community-list-page').should('exist');
|
||||
|
||||
// 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();
|
||||
|
||||
// Analyze <ds-community-list-page> for accessibility issues
|
||||
// Disable heading-order checks until it is fixed
|
||||
testA11y('ds-community-list-page',
|
||||
{
|
||||
rules: {
|
||||
'heading-order': { enabled: false }
|
||||
}
|
||||
} as Options
|
||||
);
|
||||
});
|
||||
});
|
15
cypress/integration/community-page.spec.ts
Normal file
15
cypress/integration/community-page.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { TEST_COMMUNITY } from 'cypress/support';
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Community Page', () => {
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/communities/' + TEST_COMMUNITY);
|
||||
|
||||
// <ds-community-page> tag must be loaded
|
||||
cy.get('ds-community-page').should('exist');
|
||||
|
||||
// Analyze <ds-community-page> for accessibility issues
|
||||
testA11y('ds-community-page',);
|
||||
});
|
||||
});
|
32
cypress/integration/community-statistics.spec.ts
Normal file
32
cypress/integration/community-statistics.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { TEST_COMMUNITY } from 'cypress/support';
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Community Statistics Page', () => {
|
||||
const COMMUNITYSTATISTICSPAGE = '/statistics/communities/' + TEST_COMMUNITY;
|
||||
|
||||
it('should load if you click on "Statistics" from a Community page', () => {
|
||||
cy.visit('/communities/' + 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('exist');
|
||||
});
|
||||
|
||||
it('should contain a "Total visits per month" section', () => {
|
||||
cy.visit(COMMUNITYSTATISTICSPAGE);
|
||||
cy.get('.' + TEST_COMMUNITY + '_TotalVisitsPerMonth').should('exist');
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit(COMMUNITYSTATISTICSPAGE);
|
||||
|
||||
// <ds-community-statistics-page> tag must be loaded
|
||||
cy.get('ds-community-statistics-page').should('exist');
|
||||
|
||||
// Analyze <ds-community-statistics-page> for accessibility issues
|
||||
testA11y('ds-community-statistics-page');
|
||||
});
|
||||
});
|
13
cypress/integration/footer.spec.ts
Normal file
13
cypress/integration/footer.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Footer', () => {
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/');
|
||||
|
||||
// Footer must first be visible
|
||||
cy.get('ds-footer').should('be.visible');
|
||||
|
||||
// Analyze <ds-footer> for accessibility
|
||||
testA11y('ds-footer');
|
||||
});
|
||||
});
|
19
cypress/integration/header.spec.ts
Normal file
19
cypress/integration/header.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Header', () => {
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/');
|
||||
|
||||
// Header must first be visible
|
||||
cy.get('ds-header').should('be.visible');
|
||||
|
||||
// Analyze <ds-header> for accessibility
|
||||
testA11y({
|
||||
include: ['ds-header'],
|
||||
exclude: [
|
||||
['#search-navbar-container'], // search in navbar has duplicative ID. Will be fixed in #1174
|
||||
['.dropdownLogin'] // "Log in" link has color contrast issues. Will be fixed in #1149
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
19
cypress/integration/homepage-statistics.spec.ts
Normal file
19
cypress/integration/homepage-statistics.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Site Statistics Page', () => {
|
||||
it('should load if you click on "Statistics" from homepage', () => {
|
||||
cy.visit('/');
|
||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
||||
cy.location('pathname').should('eq', '/statistics');
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/statistics');
|
||||
|
||||
// <ds-site-statistics-page> tag must be loaded
|
||||
cy.get('ds-site-statistics-page').should('exist');
|
||||
|
||||
// Analyze <ds-site-statistics-page> for accessibility issues
|
||||
testA11y('ds-site-statistics-page');
|
||||
});
|
||||
});
|
@@ -1,3 +1,5 @@
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Homepage', () => {
|
||||
beforeEach(() => {
|
||||
// All tests start with visiting homepage
|
||||
@@ -20,18 +22,11 @@ describe('Homepage', () => {
|
||||
cy.url().should('include', 'query=' + encodeURI(queryString));
|
||||
});
|
||||
|
||||
// it('should pass accessibility tests', () => {
|
||||
// // first must inject Axe into current page
|
||||
// cy.injectAxe();
|
||||
it('should pass accessibility tests', () => {
|
||||
// Wait for homepage tag to appear
|
||||
cy.get('ds-home-page').should('be.visible');
|
||||
|
||||
// // Analyze entire page for accessibility issues
|
||||
// // NOTE: this test checks accessibility of header/footer as well
|
||||
// cy.checkA11y({
|
||||
// exclude: [
|
||||
// ['#klaro'], // Klaro plugin (privacy policy popup) has color contrast issues
|
||||
// ['#search-navbar-container'], // search in navbar has duplicative ID. Will be fixed in #1174
|
||||
// ['.dropdownLogin'] // "Log in" link in header has color contrast issues
|
||||
// ],
|
||||
// });
|
||||
// });
|
||||
// Analyze <ds-home-page> for accessibility issues
|
||||
testA11y('ds-home-page');
|
||||
});
|
||||
});
|
||||
|
@@ -1,15 +1,31 @@
|
||||
describe('Item Page', () => {
|
||||
const ITEMPAGE = '/items/e98b0f27-5c19-49a0-960d-eb6ad5287067';
|
||||
const ENTITYPAGE = '/entities/publication/e98b0f27-5c19-49a0-960d-eb6ad5287067';
|
||||
import { Options } from 'cypress-axe';
|
||||
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
it('should contain element ds-item-page when navigating to an item page', () => {
|
||||
cy.visit(ENTITYPAGE);
|
||||
cy.get('ds-item-page').should('exist');
|
||||
});
|
||||
describe('Item Page', () => {
|
||||
const ITEMPAGE = '/items/' + TEST_ENTITY_PUBLICATION;
|
||||
const ENTITYPAGE = '/entities/publication/' + 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', () => {
|
||||
cy.visit(ITEMPAGE);
|
||||
cy.location('pathname').should('eq', ENTITYPAGE);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit(ENTITYPAGE);
|
||||
|
||||
// <ds-item-page> tag must be loaded
|
||||
cy.get('ds-item-page').should('exist');
|
||||
|
||||
// Analyze <ds-item-page> for accessibility issues
|
||||
// Disable heading-order checks until it is fixed
|
||||
testA11y('ds-item-page',
|
||||
{
|
||||
rules: {
|
||||
'heading-order': { enabled: false }
|
||||
}
|
||||
} as Options
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,14 @@
|
||||
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Item Statistics Page', () => {
|
||||
const ITEMUUID = 'e98b0f27-5c19-49a0-960d-eb6ad5287067';
|
||||
const ITEMSTATISTICSPAGE = '/statistics/items/' + ITEMUUID;
|
||||
const ITEMSTATISTICSPAGE = '/statistics/items/' + TEST_ENTITY_PUBLICATION;
|
||||
|
||||
it('should load if you click on "Statistics" from an Item/Entity page', () => {
|
||||
cy.visit('/entities/publication/' + TEST_ENTITY_PUBLICATION);
|
||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
||||
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
|
||||
});
|
||||
|
||||
it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => {
|
||||
cy.visit(ITEMSTATISTICSPAGE);
|
||||
@@ -8,18 +16,23 @@ describe('Item Statistics Page', () => {
|
||||
cy.get('ds-item-page').should('not.exist');
|
||||
});
|
||||
|
||||
it('should contain the item statistics page url when navigating to an item statistics page', () => {
|
||||
cy.visit(ITEMSTATISTICSPAGE);
|
||||
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
|
||||
});
|
||||
|
||||
it('should contain a "Total visits" section', () => {
|
||||
cy.visit(ITEMSTATISTICSPAGE);
|
||||
cy.get('.' + ITEMUUID + '_TotalVisits').should('exist');
|
||||
cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisits').should('exist');
|
||||
});
|
||||
|
||||
it('should contain a "Total visits per month" section', () => {
|
||||
cy.visit(ITEMSTATISTICSPAGE);
|
||||
cy.get('.' + ITEMUUID + '_TotalVisitsPerMonth').should('exist');
|
||||
cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisitsPerMonth').should('exist');
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit(ITEMSTATISTICSPAGE);
|
||||
|
||||
// <ds-item-statistics-page> tag must be loaded
|
||||
cy.get('ds-item-statistics-page').should('exist');
|
||||
|
||||
// Analyze <ds-item-statistics-page> for accessibility issues
|
||||
testA11y('ds-item-statistics-page');
|
||||
});
|
||||
});
|
||||
|
@@ -1,3 +1,6 @@
|
||||
import { Options } from 'cypress-axe';
|
||||
import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('Search Page', () => {
|
||||
// unique ID of the search form (for selecting specific elements below)
|
||||
const SEARCHFORM_ID = '#search-form';
|
||||
@@ -16,4 +19,54 @@ describe('Search Page', () => {
|
||||
cy.get(SEARCHFORM_ID + ' button.search-button').click();
|
||||
cy.url().should('include', 'query=' + encodeURI(queryString));
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', () => {
|
||||
cy.visit('/search');
|
||||
|
||||
// <ds-search-page> tag must be loaded
|
||||
cy.get('ds-search-page').should('exist');
|
||||
|
||||
// Click each filter toggle to open *every* filter
|
||||
// (As we want to scan filter section for accessibility issues as well)
|
||||
cy.get('.filter-toggle').click({ multiple: true });
|
||||
|
||||
// Analyze <ds-search-page> for accessibility issues
|
||||
testA11y(
|
||||
{
|
||||
include: ['ds-search-page'],
|
||||
exclude: [
|
||||
['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// Search filters fail these two "moderate" impact rules
|
||||
'heading-order': { enabled: false },
|
||||
'landmark-unique': { enabled: false }
|
||||
}
|
||||
} as Options
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests in Grid view', () => {
|
||||
cy.visit('/search');
|
||||
|
||||
// Click to display grid view
|
||||
// TODO: These buttons should likely have an easier way to uniquely select
|
||||
cy.get('#search-sidebar-content > ds-view-mode-switch > .btn-group > [href="/search?spc.sf=score&spc.sd=DESC&view=grid"] > .fas').click();
|
||||
|
||||
// <ds-search-page> tag must be loaded
|
||||
cy.get('ds-search-page').should('exist');
|
||||
|
||||
// Analyze <ds-search-page> for accessibility issues
|
||||
testA11y('ds-search-page',
|
||||
{
|
||||
rules: {
|
||||
// Search filters fail these two "moderate" impact rules
|
||||
'heading-order': { enabled: false },
|
||||
'landmark-unique': { enabled: false }
|
||||
}
|
||||
} as Options
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -1,5 +1,16 @@
|
||||
// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress
|
||||
// For more info, visit https://on.cypress.io/plugins-api
|
||||
/* tslint:disable:no-empty */
|
||||
module.exports = (on, config) => { };
|
||||
/* tslint:enable:no-empty */
|
||||
module.exports = (on, config) => {
|
||||
// Define "log" and "table" tasks, used for logging accessibility errors during CI
|
||||
// Borrowed from https://github.com/component-driven/cypress-axe#in-cypress-plugins-file
|
||||
on('task', {
|
||||
log(message: string) {
|
||||
console.log(message);
|
||||
return null;
|
||||
},
|
||||
table(message: string) {
|
||||
console.table(message);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -19,3 +19,8 @@
|
||||
// Import Cypress Axe tools for all tests
|
||||
// https://github.com/component-driven/cypress-axe
|
||||
import 'cypress-axe';
|
||||
|
||||
// Global constants used in tests
|
||||
export const TEST_COLLECTION = '282164f5-d325-4740-8dd1-fa4d6d3e7200';
|
||||
export const TEST_COMMUNITY = '0958c910-2037-42a9-81c7-dca80e3892b4';
|
||||
export const TEST_ENTITY_PUBLICATION = 'e98b0f27-5c19-49a0-960d-eb6ad5287067';
|
||||
|
44
cypress/support/utils.ts
Normal file
44
cypress/support/utils.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Result } from 'axe-core';
|
||||
import { Options } from 'cypress-axe';
|
||||
|
||||
// Log violations to terminal/commandline in a table format.
|
||||
// Uses 'log' and 'table' tasks defined in ../plugins/index.ts
|
||||
// Borrowed from https://github.com/component-driven/cypress-axe#in-your-spec-file
|
||||
function terminalLog(violations: Result[]) {
|
||||
cy.task(
|
||||
'log',
|
||||
`${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${violations.length === 1 ? 'was' : 'were'} detected`
|
||||
);
|
||||
// pluck specific keys to keep the table readable
|
||||
const violationData = violations.map(
|
||||
({ id, impact, description, helpUrl, nodes }) => ({
|
||||
id,
|
||||
impact,
|
||||
description,
|
||||
helpUrl,
|
||||
nodes: nodes.length,
|
||||
html: nodes.map(node => node.html)
|
||||
})
|
||||
);
|
||||
|
||||
// Print violations as an array, since 'node.html' above often breaks table alignment
|
||||
cy.task('log', violationData);
|
||||
// Optionally, uncomment to print as a table
|
||||
// cy.task('table', violationData);
|
||||
|
||||
}
|
||||
|
||||
// Custom "testA11y()" method which checks accessibility using cypress-axe
|
||||
// while also ensuring any violations are logged to the terminal (see terminalLog above)
|
||||
// This method MUST be called after cy.visit(), as cy.injectAxe() must be called after page load
|
||||
export const testA11y = (context?: any, options?: Options) => {
|
||||
cy.injectAxe();
|
||||
cy.configureAxe({
|
||||
rules: [
|
||||
// Disable color contrast checks as they are inaccurate / result in a lot of false positives
|
||||
// See also open issues in axe-core: https://github.com/dequelabs/axe-core/labels/color%20contrast
|
||||
{ id: 'color-contrast', enabled: false },
|
||||
]
|
||||
});
|
||||
cy.checkA11y(context, options, terminalLog);
|
||||
};
|
@@ -6,7 +6,8 @@
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"cypress",
|
||||
"cypress-axe"
|
||||
"cypress-axe",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
@@ -152,7 +152,7 @@
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"css-loader": "3.4.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"cypress": "8.3.1",
|
||||
"cypress": "8.6.0",
|
||||
"cypress-axe": "^0.13.0",
|
||||
"deep-freeze": "0.0.1",
|
||||
"dotenv": "^8.2.0",
|
||||
|
@@ -1,79 +1,80 @@
|
||||
<div [class.form-group]="(model.type !== 'GROUP' && asBootstrapFormGroup) || getClass('element', 'container').includes('form-group')"
|
||||
[formGroup]="group"
|
||||
[ngClass]="[getClass('element', 'container'), getClass('grid', 'container')]">
|
||||
<label *ngIf="!isCheckbox && hasLabel"
|
||||
[id]="'label_' + model.id"
|
||||
[for]="id"
|
||||
[innerHTML]="(model.required && model.label) ? (model.label | translate) + ' *' : (model.label | translate)"
|
||||
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"></label>
|
||||
<ng-container *ngTemplateOutlet="startTemplate?.templateRef; context: model"></ng-container>
|
||||
<!-- Should be *ngIf instead of class d-none, but that breaks the #componentViewContainer reference-->
|
||||
<div [ngClass]="{'form-row': model.hasLanguages || isRelationship,
|
||||
<label *ngIf="!isCheckbox && hasLabel"
|
||||
[id]="'label_' + model.id"
|
||||
[for]="id"
|
||||
[innerHTML]="(model.required && model.label) ? (model.label | translate) + ' *' : (model.label | translate)"
|
||||
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"></label>
|
||||
<ng-container *ngTemplateOutlet="startTemplate?.templateRef; context: model"></ng-container>
|
||||
<!-- Should be *ngIf instead of class d-none, but that breaks the #componentViewContainer reference-->
|
||||
<div [ngClass]="{'form-row': model.hasLanguages || isRelationship,
|
||||
'd-none': value?.isVirtual && (model.hasSelectableMetadata || context?.index > 0)}">
|
||||
<div [ngClass]="getClass('grid', 'control')">
|
||||
<ng-container #componentViewContainer></ng-container>
|
||||
<small *ngIf="hasHint && ((model.repeatable === false && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)"
|
||||
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
||||
<!-- In case of repeatable fields show empty space for all elements except the first -->
|
||||
<div *ngIf="context?.index !== null
|
||||
<div [ngClass]="getClass('grid', 'control')">
|
||||
<div>
|
||||
<ng-container #componentViewContainer></ng-container>
|
||||
</div>
|
||||
<small *ngIf="hasHint && ((model.repeatable === false && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)"
|
||||
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
||||
<!-- In case of repeatable fields show empty space for all elements except the first -->
|
||||
<div *ngIf="context?.index !== null
|
||||
&& (!showErrorMessages || errorMessages.length === 0)" class="clearfix w-100 mb-2"></div>
|
||||
|
||||
<div *ngIf="showErrorMessages" [ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
|
||||
<small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate: model.validators }}</small>
|
||||
</div>
|
||||
<div *ngIf="showErrorMessages" [id]="id + '_errors'"
|
||||
[ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
|
||||
<small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate: model.validators }}</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div *ngIf="model.languageCodes && model.languageCodes.length > 0" class="col-xs-2" >
|
||||
<select
|
||||
#language="ngModel"
|
||||
[disabled]="model.readOnly"
|
||||
[(ngModel)]="model.language"
|
||||
class="form-control"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChangeLanguage($event)"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
required>
|
||||
<option *ngFor="let lang of model.languageCodes" [value]="lang.code">{{lang.display}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div *ngIf="isRelationship" class="col-auto text-center">
|
||||
<button class="btn btn-secondary"
|
||||
type="button"
|
||||
ngbTooltip="{{'form.lookup-help' | translate}}"
|
||||
placement="top"
|
||||
(click)="openLookup(); $event.stopPropagation();"><i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngTemplateOutlet="endTemplate?.templateRef; context: model"></ng-container>
|
||||
<ng-container *ngIf="value?.isVirtual">
|
||||
<ds-existing-metadata-list-element
|
||||
*ngIf="model.hasSelectableMetadata"
|
||||
[reoRel]="relationshipValue$ | async"
|
||||
[submissionItem]="item$ | async"
|
||||
[listId]="listId"
|
||||
[metadataFields]="model.metadataFields"
|
||||
[submissionId]="model.submissionId"
|
||||
[relationshipOptions]="model.relationship"
|
||||
(remove)="onRemove()"
|
||||
>
|
||||
</ds-existing-metadata-list-element>
|
||||
<ds-existing-relation-list-element
|
||||
*ngIf="!model.hasSelectableMetadata"
|
||||
[ngClass]="{'d-block pb-2 pt-2': !context?.index}"
|
||||
[reoRel]="relationshipValue$ | async"
|
||||
[submissionItem]="item$ | async"
|
||||
[listId]="listId"
|
||||
[metadataFields]="model.metadataFields"
|
||||
[submissionId]="model.submissionId"
|
||||
[relationshipOptions]="model.relationship"
|
||||
>
|
||||
</ds-existing-relation-list-element>
|
||||
<small *ngIf="hasHint && (model.repeatable === false || context?.index === context?.context?.groups?.length - 1) && (!showErrorMessages || errorMessages.length === 0)"
|
||||
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
||||
<div class="clearfix w-100 mb-2"></div>
|
||||
</ng-container>
|
||||
<ng-content></ng-content>
|
||||
<div *ngIf="model.languageCodes && model.languageCodes.length > 0" class="col-xs-2" >
|
||||
<select
|
||||
#language="ngModel"
|
||||
[disabled]="model.readOnly"
|
||||
[(ngModel)]="model.language"
|
||||
class="form-control"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChangeLanguage($event)"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
required>
|
||||
<option *ngFor="let lang of model.languageCodes" [value]="lang.code">{{lang.display}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div *ngIf="isRelationship" class="col-auto text-center">
|
||||
<button class="btn btn-secondary"
|
||||
type="button"
|
||||
ngbTooltip="{{'form.lookup-help' | translate}}"
|
||||
placement="top"
|
||||
(click)="openLookup(); $event.stopPropagation();"><i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngTemplateOutlet="endTemplate?.templateRef; context: model"></ng-container>
|
||||
<ng-container *ngIf="value?.isVirtual">
|
||||
<ds-existing-metadata-list-element
|
||||
*ngIf="model.hasSelectableMetadata"
|
||||
[reoRel]="relationshipValue$ | async"
|
||||
[submissionItem]="item$ | async"
|
||||
[listId]="listId"
|
||||
[metadataFields]="model.metadataFields"
|
||||
[submissionId]="model.submissionId"
|
||||
[relationshipOptions]="model.relationship"
|
||||
(remove)="onRemove()"
|
||||
>
|
||||
</ds-existing-metadata-list-element>
|
||||
<ds-existing-relation-list-element
|
||||
*ngIf="!model.hasSelectableMetadata"
|
||||
[ngClass]="{'d-block pb-2 pt-2': !context?.index}"
|
||||
[reoRel]="relationshipValue$ | async"
|
||||
[submissionItem]="item$ | async"
|
||||
[listId]="listId"
|
||||
[metadataFields]="model.metadataFields"
|
||||
[submissionId]="model.submissionId"
|
||||
[relationshipOptions]="model.relationship"
|
||||
>
|
||||
</ds-existing-relation-list-element>
|
||||
<small *ngIf="hasHint && (model.repeatable === false || context?.index === context?.context?.groups?.length - 1) && (!showErrorMessages || errorMessages.length === 0)"
|
||||
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
||||
<div class="clearfix w-100 mb-2"></div>
|
||||
</ng-container>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -1,50 +1,54 @@
|
||||
<div class="d-flex">
|
||||
<ds-number-picker
|
||||
tabindex="1"
|
||||
[disabled]="model.disabled"
|
||||
[min]="minYear"
|
||||
[max]="maxYear"
|
||||
[name]="'year'"
|
||||
[size]="4"
|
||||
[(ngModel)]="initialYear"
|
||||
[value]="year"
|
||||
[invalid]="showErrorMessages"
|
||||
[placeholder]='yearPlaceholder'
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChange($event)"
|
||||
(focus)="onFocus($event)"
|
||||
></ds-number-picker>
|
||||
<div>
|
||||
<fieldset class="d-flex">
|
||||
<legend [id]="'legend_' + model.id" [ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]">
|
||||
{{model.placeholder}} <span *ngIf="model.required">*</span>
|
||||
</legend>
|
||||
<ds-number-picker
|
||||
tabindex="1"
|
||||
[disabled]="model.disabled"
|
||||
[min]="minYear"
|
||||
[max]="maxYear"
|
||||
[name]="'year'"
|
||||
[size]="4"
|
||||
[(ngModel)]="initialYear"
|
||||
[value]="year"
|
||||
[invalid]="showErrorMessages"
|
||||
[placeholder]='yearPlaceholder'
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChange($event)"
|
||||
(focus)="onFocus($event)"
|
||||
></ds-number-picker>
|
||||
|
||||
<ds-number-picker
|
||||
tabindex="2"
|
||||
[min]="minMonth"
|
||||
[max]="maxMonth"
|
||||
[name]="'month'"
|
||||
[size]="6"
|
||||
[(ngModel)]="initialMonth"
|
||||
[value]="month"
|
||||
[placeholder]="monthPlaceholder"
|
||||
[disabled]="!year || model.disabled"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChange($event)"
|
||||
(focus)="onFocus($event)"
|
||||
></ds-number-picker>
|
||||
|
||||
<ds-number-picker
|
||||
tabindex="3"
|
||||
[min]="minDay"
|
||||
[max]="maxDay"
|
||||
[name]="'day'"
|
||||
[size]="2"
|
||||
[(ngModel)]="initialDay"
|
||||
[value]="day"
|
||||
[placeholder]="dayPlaceholder"
|
||||
[disabled]="!month || model.disabled"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChange($event)"
|
||||
(focus)="onFocus($event)"
|
||||
></ds-number-picker>
|
||||
<ds-number-picker
|
||||
tabindex="2"
|
||||
[min]="minMonth"
|
||||
[max]="maxMonth"
|
||||
[name]="'month'"
|
||||
[size]="6"
|
||||
[(ngModel)]="initialMonth"
|
||||
[value]="month"
|
||||
[placeholder]="monthPlaceholder"
|
||||
[disabled]="!year || model.disabled"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChange($event)"
|
||||
(focus)="onFocus($event)"
|
||||
></ds-number-picker>
|
||||
|
||||
<ds-number-picker
|
||||
tabindex="3"
|
||||
[min]="minDay"
|
||||
[max]="maxDay"
|
||||
[name]="'day'"
|
||||
[size]="2"
|
||||
[(ngModel)]="initialDay"
|
||||
[value]="day"
|
||||
[placeholder]="dayPlaceholder"
|
||||
[disabled]="!month || model.disabled"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChange($event)"
|
||||
(focus)="onFocus($event)"
|
||||
></ds-number-picker>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
@@ -1,3 +1,7 @@
|
||||
.col-lg-1 {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
legend {
|
||||
font-size: initial;
|
||||
}
|
||||
|
@@ -69,6 +69,7 @@ describe('DsDatePickerComponent test suite', () => {
|
||||
[bindId]='bindId'
|
||||
[group]='group'
|
||||
[model]='model'
|
||||
[legend]='legend'
|
||||
(blur)='onBlur($event)'
|
||||
(change)='onValueChange($event)'
|
||||
(focus)='onFocus($event)'></ds-date-picker>`;
|
||||
|
@@ -20,6 +20,7 @@ export class DsDatePickerComponent extends DynamicFormControlComponent implement
|
||||
@Input() bindId = true;
|
||||
@Input() group: FormGroup;
|
||||
@Input() model: DynamicDsDatePickerModel;
|
||||
@Input() legend: string;
|
||||
|
||||
@Output() selected = new EventEmitter<number>();
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
|
@@ -1,24 +1,30 @@
|
||||
import {
|
||||
DynamicDateControlModel,
|
||||
DynamicDateControlModelConfig,
|
||||
DynamicDatePickerModelConfig,
|
||||
DynamicFormControlLayout,
|
||||
serializable
|
||||
} from '@ng-dynamic-forms/core';
|
||||
|
||||
export const DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER = 'DATE';
|
||||
|
||||
export interface DynamicDsDateControlModelConfig extends DynamicDatePickerModelConfig {
|
||||
legend?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic Date Picker Model class
|
||||
*/
|
||||
export class DynamicDsDatePickerModel extends DynamicDateControlModel {
|
||||
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER;
|
||||
malformedDate: boolean;
|
||||
legend: string;
|
||||
hasLanguages = false;
|
||||
repeatable = false;
|
||||
|
||||
constructor(config: DynamicDateControlModelConfig, layout?: DynamicFormControlLayout) {
|
||||
constructor(config: DynamicDsDateControlModelConfig, layout?: DynamicFormControlLayout) {
|
||||
super(config, layout);
|
||||
this.malformedDate = false;
|
||||
this.legend = config.legend;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,13 @@
|
||||
<div #sdRef="ngbDropdown" ngbDropdown display="dynamic" placement="bottom-right" class="w-100">
|
||||
<div class="position-relative right-addon">
|
||||
<div class="position-relative right-addon"
|
||||
role="combobox"
|
||||
[attr.aria-label]="model.label"
|
||||
[attr.aria-owns]="'combobox_' + id + '_listbox'">
|
||||
<i ngbDropdownToggle class="position-absolute scrollable-dropdown-toggle"
|
||||
aria-hidden="true"></i>
|
||||
<input class="form-control"
|
||||
[attr.aria-controls]="'combobox_' + id + '_listbox'"
|
||||
[attr.aria-activedescendant]="'combobox_' + id + '_selected'"
|
||||
[attr.aria-label]="model.placeholder"
|
||||
[attr.autoComplete]="model.autoComplete"
|
||||
[class.is-invalid]="showErrorMessages"
|
||||
@@ -24,6 +29,8 @@
|
||||
aria-expanded="false"
|
||||
[attr.aria-label]="model.placeholder">
|
||||
<div class="scrollable-menu"
|
||||
role="listbox"
|
||||
[id]="'combobox_' + id + '_listbox'"
|
||||
[attr.aria-label]="model.placeholder"
|
||||
infiniteScroll
|
||||
[infiniteScrollDistance]="2"
|
||||
@@ -32,7 +39,10 @@
|
||||
[scrollWindow]="false">
|
||||
|
||||
<button class="dropdown-item disabled" *ngIf="optionsList && optionsList.length == 0">{{'form.no-results' | translate}}</button>
|
||||
<button class="dropdown-item collection-item text-truncate" *ngFor="let listEntry of optionsList" (click)="onSelect(listEntry); sdRef.close()" title="{{ listEntry.display }}">
|
||||
<button class="dropdown-item collection-item text-truncate" *ngFor="let listEntry of optionsList"
|
||||
(click)="onSelect(listEntry); sdRef.close()"
|
||||
title="{{ listEntry.display }}" role="option"
|
||||
[attr.id]="listEntry.display == (currentValue|async) ? ('combobox_' + id + '_selected') : null">
|
||||
{{inputFormatter(listEntry)}}
|
||||
</button>
|
||||
<div class="scrollable-dropdown-loading text-center" *ngIf="loading"><p>{{'form.loading' | translate}}</p></div>
|
||||
@@ -40,5 +50,3 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { FieldParser } from './field-parser';
|
||||
import { DynamicDatePickerModelConfig } from '@ng-dynamic-forms/core';
|
||||
import { DynamicDsDatePickerModel } from '../ds-dynamic-form-ui/models/date-picker/date-picker.model';
|
||||
import {
|
||||
DynamicDsDateControlModelConfig,
|
||||
DynamicDsDatePickerModel
|
||||
} from '../ds-dynamic-form-ui/models/date-picker/date-picker.model';
|
||||
import { isNotEmpty } from '../../../empty.util';
|
||||
import { DS_DATE_PICKER_SEPARATOR } from '../ds-dynamic-form-ui/models/date-picker/date-picker.component';
|
||||
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
|
||||
@@ -9,7 +11,8 @@ export class DateFieldParser extends FieldParser {
|
||||
|
||||
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
|
||||
let malformedDate = false;
|
||||
const inputDateModelConfig: DynamicDatePickerModelConfig = this.initModel(null, label);
|
||||
const inputDateModelConfig: DynamicDsDateControlModelConfig = this.initModel(null, false, true);
|
||||
inputDateModelConfig.legend = this.configData.label;
|
||||
|
||||
inputDateModelConfig.toggleIcon = 'fas fa-calendar';
|
||||
this.setValues(inputDateModelConfig as any, fieldValue);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<div class="d-flex flex-column align-items-center justify-content-around mr-3">
|
||||
<button
|
||||
class="btn btn-link"
|
||||
class="btn btn-link-focus"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
[disabled]="disabled"
|
||||
@@ -25,7 +25,7 @@
|
||||
aria-label="name"
|
||||
>
|
||||
<button
|
||||
class="btn btn-link"
|
||||
class="btn btn-link-focus"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
[disabled]="disabled"
|
||||
|
@@ -23,3 +23,24 @@
|
||||
input {
|
||||
max-width: 80px !important;
|
||||
}
|
||||
|
||||
.btn-link-focus {
|
||||
// behave as btn-link but does not override box-shadow of btn-link:focus
|
||||
font-weight: $font-weight-normal;
|
||||
color: $link-color;
|
||||
text-decoration: $link-decoration;
|
||||
@include hover {
|
||||
color: $link-hover-color;
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
color: $btn-link-disabled-color;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.focus {
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
export const mockDynamicFormLayoutService = jasmine.createSpyObj('DynamicFormLayoutService', {
|
||||
getElementId: jasmine.createSpy('getElementId')
|
||||
getElementId: jasmine.createSpy('getElementId'),
|
||||
getClass: 'class',
|
||||
});
|
||||
|
||||
export const mockDynamicFormValidationService = jasmine.createSpyObj('DynamicFormValidationService', {
|
||||
|
@@ -19,11 +19,14 @@
|
||||
(fileOver)="fileOverBase($event)"
|
||||
class="well ds-base-drop-zone mt-1 mb-3 text-muted">
|
||||
<div class="text-center m-0 p-2 d-flex justify-content-center align-items-center" *ngIf="uploader?.queue?.length === 0">
|
||||
<span><i class="fas fa-upload" aria-hidden="true"></i> {{dropMsg | translate}} {{'uploader.or' | translate}}</span>
|
||||
<label for="inputFileUploader" class="btn btn-link m-0 p-0 ml-1" tabindex="0" (keyup.enter)="$event.stopImmediatePropagation(); fileInput.click()">
|
||||
<input #fileInput id="inputFileUploader" class="d-none" type="file" role="button" ng2FileSelect [uploader]="uploader" multiple tabindex="0" />
|
||||
{{'uploader.browse' | translate}}
|
||||
</label>
|
||||
<span>
|
||||
<i class="fas fa-upload" aria-hidden="true"></i>
|
||||
{{dropMsg | translate}}{{'uploader.or' | translate}}
|
||||
<label for="inputFileUploader" class="btn btn-link m-0 p-0 ml-1" tabindex="0" (keyup.enter)="$event.stopImmediatePropagation(); fileInput.click()">
|
||||
<span role="button" [attr.aria-label]="'uploader.browse' | translate">{{'uploader.browse' | translate}}</span>
|
||||
</label>
|
||||
<input #fileInput id="inputFileUploader" class="d-none" type="file" ng2FileSelect [uploader]="uploader" multiple tabindex="0" />
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="(isOverBaseDropZone | async) || uploader?.queue?.length !== 0">
|
||||
<div class="m-1">
|
||||
|
@@ -17,3 +17,23 @@
|
||||
z-index: var(--ds-submission-footer-z-index);
|
||||
}
|
||||
|
||||
.btn-link-focus {
|
||||
// behave as btn-link but does not override box-shadow of btn-link:focus
|
||||
font-weight: $font-weight-normal;
|
||||
color: $link-color;
|
||||
text-decoration: $link-decoration;
|
||||
@include hover {
|
||||
color: $link-hover-color;
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
color: $btn-link-disabled-color;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.focus {
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
}
|
||||
|
@@ -15,15 +15,15 @@
|
||||
<span class="float-left section-title" tabindex="0">{{ 'submission.sections.'+sectionData.header | translate }}</span>
|
||||
<div class="d-inline-block float-right">
|
||||
<i *ngIf="!(sectionRef.isValid() | async) && !(sectionRef.hasErrors())" class="fas fa-exclamation-circle text-warning mr-3"
|
||||
aria-hidden="true" title="{{'submission.sections.status.warnings.title' | translate}}"></i>
|
||||
title="{{'submission.sections.status.warnings.title' | translate}}" role="img" [attr.aria-label]="'submission.sections.status.warnings.aria' | translate"></i>
|
||||
<i *ngIf="(sectionRef.hasErrors())" class="fas fa-exclamation-circle text-danger mr-3"
|
||||
aria-hidden="true" title="{{'submission.sections.status.errors.title' | translate}}"></i>
|
||||
title="{{'submission.sections.status.errors.title' | translate}}" role="img" [attr.aria-label]="'submission.sections.status.errors.aria' | translate"></i>
|
||||
<i *ngIf="(sectionRef.isValid() | async) && !(sectionRef.hasErrors())" class="fas fa-check-circle text-success mr-3"
|
||||
aria-hidden="true" title="{{'submission.sections.status.valid.title' | translate}}"></i>
|
||||
title="{{'submission.sections.status.valid.title' | translate}}" role="img" [attr.aria-label]="'submission.sections.status.valid.aria' | translate"></i>
|
||||
<a class="close"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
[attr.aria-label]="(sectionRef.isOpen() ? 'submission.sections.toggle.close' : 'submission.sections.toggle.open') | translate"
|
||||
[attr.aria-label]="(sectionRef.isOpen() ? 'submission.sections.toggle.aria.close' : 'submission.sections.toggle.aria.open') | translate: {sectionHeader: ('submission.sections.'+sectionData.header | translate)}"
|
||||
[title]="(sectionRef.isOpen() ? 'submission.sections.toggle.close' : 'submission.sections.toggle.open') | translate">
|
||||
<span *ngIf="sectionRef.isOpen()" class="fas fa-chevron-up fa-fw"></span>
|
||||
<span *ngIf="!sectionRef.isOpen()" class="fas fa-chevron-down fa-fw"></span>
|
||||
|
@@ -74,7 +74,7 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG: DynamicDatePicke
|
||||
required: null
|
||||
},
|
||||
errorMessages: {
|
||||
required: 'submission.sections.upload.form.date-required'
|
||||
required: 'submission.sections.upload.form.date-required-from'
|
||||
}
|
||||
};
|
||||
export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT: DynamicFormControlLayout = {
|
||||
@@ -104,7 +104,7 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_CONFIG: DynamicDatePickerM
|
||||
required: null
|
||||
},
|
||||
errorMessages: {
|
||||
required: 'submission.sections.upload.form.date-required'
|
||||
required: 'submission.sections.upload.form.date-required-until'
|
||||
}
|
||||
};
|
||||
export const BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT: DynamicFormControlLayout = {
|
||||
|
@@ -10,16 +10,16 @@
|
||||
</div>
|
||||
<div class="float-right w-15" [class.sticky-buttons]="!readMode">
|
||||
<ng-container *ngIf="readMode">
|
||||
<ds-file-download-link [cssClasses]="'btn btn-link'" [isBlank]="true" [bitstream]="getBitstream()">
|
||||
<ds-file-download-link [cssClasses]="'btn btn-link-focus'" [isBlank]="true" [bitstream]="getBitstream()">
|
||||
<i class="fa fa-download fa-2x text-normal" aria-hidden="true"></i>
|
||||
</ds-file-download-link>
|
||||
<button class="btn btn-link"
|
||||
<button class="btn btn-link-focus"
|
||||
[attr.aria-label]="'submission.sections.upload.edit.title' | translate"
|
||||
title="{{ 'submission.sections.upload.edit.title' | translate }}"
|
||||
(click)="$event.preventDefault();switchMode();">
|
||||
<i class="fa fa-edit fa-2x text-normal"></i>
|
||||
</button>
|
||||
<button class="btn btn-link"
|
||||
<button class="btn btn-link-focus"
|
||||
[attr.aria-label]="'submission.sections.upload.delete.confirm.title' | translate"
|
||||
title="{{ 'submission.sections.upload.delete.confirm.title' | translate }}"
|
||||
[disabled]="(processingDelete$ | async)"
|
||||
@@ -29,17 +29,17 @@
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!readMode">
|
||||
<button class="btn btn-link"
|
||||
<button class="btn btn-link-focus"
|
||||
[attr.aria-label]="'submission.sections.upload.save-metadata' | translate"
|
||||
title="{{ 'submission.sections.upload.save-metadata' | translate }}"
|
||||
(click)="saveBitstreamData($event);">
|
||||
<i class="fa fa-save fa-2x text-success"></i>
|
||||
</button>
|
||||
<button class="btn btn-link"
|
||||
<button class="btn btn-link-focus"
|
||||
[attr.aria-label]="'submission.sections.upload.undo' | translate"
|
||||
title="{{ 'submission.sections.upload.undo' | translate }}"
|
||||
(click)="$event.preventDefault();switchMode();"><i class="fa fa-ban fa-2x text-warning"></i></button>
|
||||
<button class="btn btn-link"
|
||||
<button class="btn btn-link-focus"
|
||||
[attr.aria-label]="'submission.sections.upload.delete.confirm.title' | translate"
|
||||
title="{{ 'submission.sections.upload.delete.confirm.title' | translate }}"
|
||||
[disabled]="(processingDelete$ | async)"
|
||||
|
@@ -3678,10 +3678,20 @@
|
||||
|
||||
"submission.sections.status.warnings.title": "Warnings",
|
||||
|
||||
"submission.sections.status.errors.aria": "has errors",
|
||||
|
||||
"submission.sections.status.valid.aria": "is valid",
|
||||
|
||||
"submission.sections.status.warnings.aria": "has warnings",
|
||||
|
||||
"submission.sections.toggle.open": "Open section",
|
||||
|
||||
"submission.sections.toggle.close": "Close section",
|
||||
|
||||
"submission.sections.toggle.aria.open": "Expand {{sectionHeader}} section",
|
||||
|
||||
"submission.sections.toggle.aria.close": "Collapse {{sectionHeader}} section",
|
||||
|
||||
"submission.sections.upload.delete.confirm.cancel": "Cancel",
|
||||
|
||||
"submission.sections.upload.delete.confirm.info": "This operation can't be undone. Are you sure?",
|
||||
@@ -3702,6 +3712,10 @@
|
||||
|
||||
"submission.sections.upload.form.date-required": "Date is required.",
|
||||
|
||||
"submission.sections.upload.form.date-required-from": "Grant access from date is required.",
|
||||
|
||||
"submission.sections.upload.form.date-required-until": "Grant access until date is required.",
|
||||
|
||||
"submission.sections.upload.form.from-label": "Grant access from",
|
||||
|
||||
"submission.sections.upload.form.from-placeholder": "From",
|
||||
|
11573
src/assets/i18n/es.json5
11573
src/assets/i18n/es.json5
File diff suppressed because it is too large
Load Diff
@@ -158,25 +158,17 @@ export const environment: GlobalConfig = {
|
||||
code: 'en',
|
||||
label: 'English',
|
||||
active: true,
|
||||
}, {
|
||||
code: 'de',
|
||||
label: 'Deutsch',
|
||||
active: true,
|
||||
}, {
|
||||
code: 'cs',
|
||||
label: 'Čeština',
|
||||
active: true,
|
||||
}, {
|
||||
code: 'nl',
|
||||
label: 'Nederlands',
|
||||
code: 'de',
|
||||
label: 'Deutsch',
|
||||
active: true,
|
||||
},{
|
||||
code: 'pt-BR',
|
||||
label: 'Português do Brasil',
|
||||
active: true,
|
||||
},{
|
||||
code: 'pt-PT',
|
||||
label: 'Português',
|
||||
}, {
|
||||
code: 'es',
|
||||
label: 'Español',
|
||||
active: true,
|
||||
}, {
|
||||
code: 'fr',
|
||||
@@ -186,14 +178,26 @@ export const environment: GlobalConfig = {
|
||||
code: 'lv',
|
||||
label: 'Latviešu',
|
||||
active: true,
|
||||
}, {
|
||||
code: 'hu',
|
||||
label: 'Magyar',
|
||||
active: true,
|
||||
}, {
|
||||
code: 'nl',
|
||||
label: 'Nederlands',
|
||||
active: true,
|
||||
}, {
|
||||
code: 'pt-PT',
|
||||
label: 'Português',
|
||||
active: true,
|
||||
},{
|
||||
code: 'pt-BR',
|
||||
label: 'Português do Brasil',
|
||||
active: true,
|
||||
},{
|
||||
code: 'fi',
|
||||
label: 'Suomi',
|
||||
active: true,
|
||||
},{
|
||||
code: 'hu',
|
||||
label: 'magyar',
|
||||
active: true,
|
||||
}],
|
||||
// Browse-By Pages
|
||||
browseBy: {
|
||||
|
14
yarn.lock
14
yarn.lock
@@ -4153,10 +4153,10 @@ cypress-axe@^0.13.0:
|
||||
resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.13.0.tgz#3234e1a79a27701f2451fcf2f333eb74204c7966"
|
||||
integrity sha512-fCIy7RiDCm7t30U3C99gGwQrUO307EYE1QqXNaf9ToK4DVqW8y5on+0a/kUHMrHdlls2rENF6TN9ZPpPpwLrnw==
|
||||
|
||||
cypress@8.3.1:
|
||||
version "8.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.3.1.tgz#c6760dbb907df2570b0e1ac235fa31c30f9260a6"
|
||||
integrity sha512-1v6pfx+/5cXhaT5T6QKOvnkawmEHWHLiVzm3MYMoQN1fkX2Ma1C32STd3jBStE9qT5qPSTILjGzypVRxCBi40g==
|
||||
cypress@8.6.0:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.6.0.tgz#8d02fa58878b37cfc45bbfce393aa974fa8a8e22"
|
||||
integrity sha512-F7qEK/6Go5FsqTueR+0wEw2vOVKNgk5847Mys8vsWkzPoEKdxs+7N9Y1dit+zhaZCLtMPyrMwjfA53ZFy+lSww==
|
||||
dependencies:
|
||||
"@cypress/request" "^2.88.6"
|
||||
"@cypress/xvfb" "^1.2.4"
|
||||
@@ -4192,6 +4192,7 @@ cypress@8.3.1:
|
||||
minimist "^1.2.5"
|
||||
ospath "^1.2.2"
|
||||
pretty-bytes "^5.6.0"
|
||||
proxy-from-env "1.0.0"
|
||||
ramda "~0.27.1"
|
||||
request-progress "^3.0.0"
|
||||
supports-color "^8.1.1"
|
||||
@@ -9839,6 +9840,11 @@ proxy-addr@~2.0.5:
|
||||
forwarded "~0.1.2"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
proxy-from-env@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
|
||||
integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=
|
||||
|
||||
prr@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||
|
Reference in New Issue
Block a user