diff --git a/README.md b/README.md index 1982fac452..065d313005 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,9 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con The test files can be found in the `./cypress/integration/` folder. -Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `config.prod.yml` or `config.yml`. You may override this using env variables, see [Configuring](#configuring). +Before you can run e2e tests, two things are required: +1. You MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `config.prod.yml` or `config.yml`. You may override this using env variables, see [Configuring](#configuring). +2. Your backend MUST include our Entities Test Data set. Some tests run against a (currently hardcoded) Community/Collection/Item UUID. These UUIDs are all valid for our Entities Test Data set. The Entities Test Data set may be installed easily via Docker, see https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker-compose#ingest-option-2-ingest-entities-test-data Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results. diff --git a/cypress.json b/cypress.json index cded267c48..e06de8e4c5 100644 --- a/cypress.json +++ b/cypress.json @@ -5,5 +5,6 @@ "screenshotsFolder": "cypress/screenshots", "pluginsFile": "cypress/plugins/index.ts", "fixturesFolder": "cypress/fixtures", - "baseUrl": "http://localhost:4000" + "baseUrl": "http://localhost:4000", + "retries": 2 } \ No newline at end of file diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index d2d02f0a55..18fa152c9d 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -64,7 +64,7 @@ services: dspacesolr: container_name: dspacesolr # Uses official Solr image at https://hub.docker.com/_/solr/ - image: solr:8.8 + image: solr:8.11-slim # Needs main 'dspace' container to start first to guarantee access to solr_configs depends_on: - dspace diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index 370ccbbdf1..3534682afc 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -62,7 +62,7 @@ services: dspacesolr: container_name: dspacesolr # Uses official Solr image at https://hub.docker.com/_/solr/ - image: solr:8.8 + image: solr:8.11-slim # Needs main 'dspace' container to start first to guarantee access to solr_configs depends_on: - dspace diff --git a/package.json b/package.json index 097f9f21ab..002ee3bd5c 100644 --- a/package.json +++ b/package.json @@ -97,9 +97,9 @@ "jwt-decode": "^3.1.2", "klaro": "^0.7.10", "lodash": "^4.17.21", - "mirador": "^3.0.0", + "mirador": "^3.3.0", "mirador-dl-plugin": "^0.13.0", - "mirador-share-plugin": "^0.10.0", + "mirador-share-plugin": "^0.11.0", "moment": "^2.29.1", "morgan": "^1.10.0", "ng-mocks": "11.11.2", @@ -112,8 +112,6 @@ "nouislider": "^14.6.3", "pem": "1.14.4", "postcss-cli": "^8.3.0", - "react": "^16.14.0", - "react-dom": "^16.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^6.6.3", "sortablejs": "1.13.0", @@ -175,6 +173,8 @@ "protractor": "^7.0.0", "protractor-istanbul-plugin": "2.0.0", "raw-loader": "0.5.1", + "react": "^16.14.0", + "react-dom": "^16.14.0", "rimraf": "^3.0.2", "rxjs-spy": "^7.5.3", "sass-resources-loader": "^2.1.1", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 26d9a5dc0d..04d2c55bdd 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -203,7 +203,6 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu ]} ],{ onSameUrlNavigation: 'reload', - relativeLinkResolution: 'legacy' }) ], exports: [RouterModule], diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index d33bade8c2..a892e34a5a 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -174,7 +174,8 @@ describe('App component', () => { TestBed.configureTestingModule(getDefaultTestBedConf()); TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')}); document = TestBed.inject(DOCUMENT); - headSpy = jasmine.createSpyObj('head', ['appendChild']); + headSpy = jasmine.createSpyObj('head', ['appendChild', 'getElementsByClassName']); + headSpy.getElementsByClassName.and.returnValue([]); spyOn(document, 'getElementsByTagName').and.returnValue([headSpy]); fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b1039f9c6d..669411d9aa 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -34,12 +34,12 @@ import { AuthService } from './core/auth/auth.service'; import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; import { MenuService } from './shared/menu/menu.service'; import { HostWindowService } from './shared/host-window.service'; -import { ThemeConfig } from '../config/theme.model'; +import { HeadTagConfig, ThemeConfig } from '../config/theme.model'; import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; import { environment } from '../environments/environment'; import { models } from './core/core.module'; import { LocaleService } from './core/locale/locale.service'; -import { hasValue, isNotEmpty } from './shared/empty.util'; +import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util'; import { KlaroService } from './shared/cookies/klaro.service'; import { GoogleAnalyticsService } from './statistics/google-analytics.service'; import { ThemeService } from './shared/theme-support/theme.service'; @@ -124,13 +124,13 @@ export class AppComponent implements OnInit, AfterViewInit { this.isThemeCSSLoading$.next(true); } if (hasValue(themeName)) { - this.setThemeCss(themeName); + this.loadGlobalThemeConfig(themeName); } else { const defaultThemeConfig = getDefaultThemeConfig(); if (hasValue(defaultThemeConfig)) { - this.setThemeCss(defaultThemeConfig.name); + this.loadGlobalThemeConfig(defaultThemeConfig.name); } else { - this.setThemeCss(BASE_THEME_NAME); + this.loadGlobalThemeConfig(BASE_THEME_NAME); } } }); @@ -245,6 +245,11 @@ export class AppComponent implements OnInit, AfterViewInit { } } + private loadGlobalThemeConfig(themeName: string): void { + this.setThemeCss(themeName); + this.setHeadTags(themeName); + } + /** * Update the theme css file in * @@ -253,9 +258,13 @@ export class AppComponent implements OnInit, AfterViewInit { */ private setThemeCss(themeName: string): void { const head = this.document.getElementsByTagName('head')[0]; + if (hasNoValue(head)) { + return; + } + // Array.from to ensure we end up with an array, not an HTMLCollection, which would be // automatically updated if we add nodes later - const currentThemeLinks = Array.from(this.document.getElementsByClassName('theme-css')); + const currentThemeLinks = Array.from(head.getElementsByClassName('theme-css')); const link = this.document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('type', 'text/css'); @@ -277,6 +286,78 @@ export class AppComponent implements OnInit, AfterViewInit { head.appendChild(link); } + private setHeadTags(themeName: string): void { + const head = this.document.getElementsByTagName('head')[0]; + if (hasNoValue(head)) { + return; + } + + // clear head tags + const currentHeadTags = Array.from(head.getElementsByClassName('theme-head-tag')); + if (hasValue(currentHeadTags)) { + currentHeadTags.forEach((currentHeadTag: any) => currentHeadTag.remove()); + } + + // create new head tags (not yet added to DOM) + const headTagFragment = this.document.createDocumentFragment(); + this.createHeadTags(themeName) + .forEach(newHeadTag => headTagFragment.appendChild(newHeadTag)); + + // add new head tags to DOM + head.appendChild(headTagFragment); + } + + private createHeadTags(themeName: string): HTMLElement[] { + const themeConfig = this.themeService.getThemeConfigFor(themeName); + const headTagConfigs = themeConfig?.headTags; + + if (hasNoValue(headTagConfigs)) { + const parentThemeName = themeConfig?.extends; + if (hasValue(parentThemeName)) { + // inherit the head tags of the parent theme + return this.createHeadTags(parentThemeName); + } + const defaultThemeConfig = getDefaultThemeConfig(); + const defaultThemeName = defaultThemeConfig.name; + if ( + hasNoValue(defaultThemeName) || + themeName === defaultThemeName || + themeName === BASE_THEME_NAME + ) { + // last resort, use fallback favicon.ico + return [ + this.createHeadTag({ + 'tagName': 'link', + 'attributes': { + 'rel': 'icon', + 'href': 'assets/images/favicon.ico', + 'sizes': 'any', + } + }) + ]; + } + + // inherit the head tags of the default theme + return this.createHeadTags(defaultThemeConfig.name); + } + + return headTagConfigs.map(this.createHeadTag.bind(this)); + } + + private createHeadTag(headTagConfig: HeadTagConfig): HTMLElement { + const tag = this.document.createElement(headTagConfig.tagName); + + if (hasValue(headTagConfig.attributes)) { + Object.entries(headTagConfig.attributes) + .forEach(([key, value]) => tag.setAttribute(key, value)); + } + + // 'class' attribute should always be 'theme-head-tag' for removal + tag.setAttribute('class', 'theme-head-tag'); + + return tag; + } + private trackIdleModal() { const isIdle$ = this.authService.isUserIdle(); const isAuthenticated$ = this.authService.isAuthenticated(); diff --git a/src/app/breadcrumbs/breadcrumbs.component.html b/src/app/breadcrumbs/breadcrumbs.component.html index beb5039178..51524fde48 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.html +++ b/src/app/breadcrumbs/breadcrumbs.component.html @@ -10,11 +10,11 @@ - + - + diff --git a/src/app/breadcrumbs/breadcrumbs.component.scss b/src/app/breadcrumbs/breadcrumbs.component.scss index 6967b53de1..412dca87db 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.scss +++ b/src/app/breadcrumbs/breadcrumbs.component.scss @@ -10,6 +10,19 @@ background-color: var(--ds-breadcrumb-bg); } +li.breadcrumb-item { + display: flex; +} + +.breadcrumb-item-limiter { + display: inline-block; + max-width: var(--ds-breadcrumb-max-length); + > * { + max-width: 100%; + display: block; + } +} + li.breadcrumb-item > a { color: var(--ds-breadcrumb-link-color) !important; } @@ -18,5 +31,6 @@ li.breadcrumb-item.active { } .breadcrumb-item+ .breadcrumb-item::before { + display: block; content: quote("•") !important; } diff --git a/src/app/breadcrumbs/breadcrumbs.component.spec.ts b/src/app/breadcrumbs/breadcrumbs.component.spec.ts index b63a7cee20..69387e7534 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.spec.ts +++ b/src/app/breadcrumbs/breadcrumbs.component.spec.ts @@ -72,7 +72,7 @@ describe('BreadcrumbsComponent', () => { expect(breadcrumbs.length).toBe(3); expectBreadcrumb(breadcrumbs[0], 'home.breadcrumbs', '/'); expectBreadcrumb(breadcrumbs[1], 'bc 1', '/example.com'); - expectBreadcrumb(breadcrumbs[2], 'bc 2', null); + expectBreadcrumb(breadcrumbs[2].query(By.css('.text-truncate')), 'bc 2', null); }); }); diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html index fa4c06d36a..6d88c9761b 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html @@ -1,10 +1,10 @@ diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.html index 71aeb79c35..b7cb645e31 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.html +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.html @@ -1,10 +1,10 @@ diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.html index 05db5b8702..988fb2d4b5 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.html +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.html @@ -1,10 +1,10 @@ diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.html index ae1d8c7510..d711ad7c18 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.html +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.html @@ -1,10 +1,10 @@ diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html index 62014f06bd..ccb37eb6ac 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html @@ -1,18 +1,24 @@
-
- {{ bitstreamName }} +
+ + {{ bitstreamName }} +
+ {{ bitstream?.firstMetadataValue('dc.description') }} +
- {{ (format$ | async)?.shortDescription }} + + {{ (format$ | async)?.shortDescription }} +
diff --git a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html index fe46906f47..f5543af971 100644 --- a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html +++ b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html @@ -26,7 +26,7 @@
- {{metadata?.value}} + {{metadata?.value}}