diff --git a/.eslintrc.json b/.eslintrc.json index cb5775ef1ff..50a9be3d59b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -152,7 +152,6 @@ } ], "@angular-eslint/no-attribute-decorator": "error", - "@angular-eslint/no-forward-ref": "error", "@angular-eslint/no-output-native": "warn", "@angular-eslint/no-output-on-prefix": "warn", "@angular-eslint/no-conflicting-lifecycle": "warn", diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 52f20470a3c..f6ffa5e004b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,12 +33,12 @@ jobs: #CHROME_VERSION: "90.0.4430.212-1" # Bump Node heap size (OOM in CI after upgrading to Angular 15) NODE_OPTIONS: '--max-old-space-size=4096' - # Project name to use when running docker-compose prior to e2e tests + # Project name to use when running "docker compose" prior to e2e tests COMPOSE_PROJECT_NAME: 'ci' strategy: # Create a matrix of Node versions to test against (in parallel) matrix: - node-version: [16.x, 18.x] + node-version: [18.x, 20.x] # Do NOT exit immediately if one matrix job fails fail-fast: false # These are the actual CI steps to perform per job @@ -74,7 +74,7 @@ jobs: id: yarn-cache-dir-path run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Cache Yarn dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: # Cache entire Yarn cache directory (see previous step) path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -101,19 +101,19 @@ jobs: # so that it can be shared with the 'codecov' job (see below) # NOTE: Angular CLI only supports code coverage for specs. See https://github.com/angular/angular-cli/issues/6286 - name: Upload code coverage report to Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: matrix.node-version == '18.x' with: - name: dspace-angular coverage report + name: coverage-report-${{ matrix.node-version }} path: 'coverage/dspace-angular/lcov.info' retention-days: 14 - # Using docker-compose start backend using CI configuration + # Using "docker compose" start backend using CI configuration # and load assetstore from a cached copy - name: Start DSpace REST Backend via Docker (for e2e tests) run: | - docker-compose -f ./docker/docker-compose-ci.yml up -d - docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli + docker compose -f ./docker/docker-compose-ci.yml up -d + docker compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli docker container ls # Run integration tests via Cypress.io @@ -135,19 +135,19 @@ jobs: # Cypress always creates a video of all e2e tests (whether they succeeded or failed) # Save those in an Artifact - name: Upload e2e test videos to Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: - name: e2e-test-videos + name: e2e-test-videos-${{ matrix.node-version }} path: cypress/videos # If e2e tests fail, Cypress creates a screenshot of what happened # Save those in an Artifact - name: Upload e2e test failure screenshots to Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: - name: e2e-test-screenshots + name: e2e-test-screenshots-${{ matrix.node-version }} path: cypress/screenshots - name: Stop app (in case it stays up after e2e tests) @@ -182,7 +182,7 @@ jobs: run: kill -9 $(lsof -t -i:4000) - name: Shutdown Docker containers - run: docker-compose -f ./docker/docker-compose-ci.yml down + run: docker compose -f ./docker/docker-compose-ci.yml down # Codecov upload is a separate job in order to allow us to restart this separate from the entire build/test # job above. This is necessary because Codecov uploads seem to randomly fail at times. @@ -197,7 +197,7 @@ jobs: # Download artifacts from previous 'tests' job - name: Download coverage artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 # Now attempt upload to Codecov using its action. # NOTE: We use a retry action to retry the Codecov upload if it fails the first time. @@ -207,11 +207,12 @@ jobs: - name: Upload coverage to Codecov.io uses: Wandalen/wretry.action@v1.3.0 with: - action: codecov/codecov-action@v3 + action: codecov/codecov-action@v4 # Ensure codecov-action throws an error when it fails to upload # This allows us to auto-restart the action if an error is thrown with: | fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} # Try re-running action 5 times max attempt_limit: 5 # Run again in 30 seconds diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 85a72161131..d0b4cd0939f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,7 +28,7 @@ jobs: # Use the reusable-docker-build.yml script from DSpace/DSpace repo to build our Docker image uses: DSpace/DSpace/.github/workflows/reusable-docker-build.yml@main with: - build_id: dspace-angular + build_id: dspace-angular-dev image_name: dspace/dspace-angular dockerfile_path: ./Dockerfile secrets: diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index b4436dca3aa..0a35a6a9504 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -16,7 +16,7 @@ jobs: # Only add to project board if issue is flagged as "needs triage" or has no labels # NOTE: By default we flag new issues as "needs triage" in our issue template if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '') - uses: actions/add-to-project@v0.5.0 + uses: actions/add-to-project@v1.0.0 # Note, the authentication token below is an ORG level Secret. # It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml index f16e81c9fd2..bbac52af243 100644 --- a/.github/workflows/pull_request_opened.yml +++ b/.github/workflows/pull_request_opened.yml @@ -21,4 +21,4 @@ jobs: # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards # See https://github.com/toshimaru/auto-author-assign - name: Assign PR to creator - uses: toshimaru/auto-author-assign@v2.0.1 + uses: toshimaru/auto-author-assign@v2.1.0 diff --git a/config/config.example.yml b/config/config.example.yml index 36d6a009d35..82c061dab22 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -17,6 +17,13 @@ ui: # Trust X-FORWARDED-* headers from proxies (default = true) useProxies: true +universal: + # Whether to inline "critical" styles into the server-side rendered HTML. + # Determining which styles are critical is a relatively expensive operation; + # this option can be disabled to boost server performance at the expense of + # loading smoothness. + inlineCriticalCss: true + # The REST API server settings # NOTE: these settings define which (publicly available) REST API to use. They are usually # 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. @@ -400,10 +407,11 @@ mediaViewer: # Whether the end user agreement is required before users use the repository. # If enabled, the user will be required to accept the agreement before they can use the repository. -# And whether the privacy statement should exist or not. +# And whether the privacy statement/COAR notify support page should exist or not. info: enableEndUserAgreement: true enablePrivacyStatement: true + enableCOARNotifySupport: true # Whether to enable Markdown (https://commonmark.org/) and MathJax (https://www.mathjax.org/) # display in supported metadata fields. By default, only dc.description.abstract is supported. diff --git a/cypress/e2e/admin-sidebar.cy.ts b/cypress/e2e/admin-sidebar.cy.ts index 7612eb53132..be1c9d4ef27 100644 --- a/cypress/e2e/admin-sidebar.cy.ts +++ b/cypress/e2e/admin-sidebar.cy.ts @@ -1,28 +1,28 @@ -import { Options } from 'cypress-axe'; import { testA11y } from 'cypress/support/utils'; +import { Options } from 'cypress-axe'; describe('Admin Sidebar', () => { - 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')); - }); + 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('should be pinnable and pass accessibility tests', () => { - // Pin the sidebar open - cy.get('#sidebar-collapse-toggle').click(); + it('should be pinnable and pass accessibility tests', () => { + // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').click(); - // Click on every expandable section to open all menus - cy.get('ds-expandable-admin-sidebar-section').click({multiple: true}); + // Click on every expandable section to open all menus + cy.get('ds-expandable-admin-sidebar-section').click({ multiple: true }); - // Analyze for accessibility - testA11y('ds-admin-sidebar', + // Analyze for accessibility + testA11y('ds-admin-sidebar', { - rules: { - // Currently all expandable sections have nested interactive elements - // See https://github.com/DSpace/dspace-angular/issues/2178 - 'nested-interactive': { enabled: false }, - } + rules: { + // Currently all expandable sections have nested interactive elements + // See https://github.com/DSpace/dspace-angular/issues/2178 + 'nested-interactive': { enabled: false }, + }, } as Options); - }); + }); }); diff --git a/cypress/e2e/breadcrumbs.cy.ts b/cypress/e2e/breadcrumbs.cy.ts index 0cddbc723c6..f660f47a540 100644 --- a/cypress/e2e/breadcrumbs.cy.ts +++ b/cypress/e2e/breadcrumbs.cy.ts @@ -1,14 +1,14 @@ 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/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'))); + it('should pass accessibility tests', () => { + // Visit an Item, as those have more breadcrumbs + cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'))); - // Wait for breadcrumbs to be visible - cy.get('ds-breadcrumbs').should('be.visible'); + // Wait for breadcrumbs to be visible + cy.get('ds-breadcrumbs').should('be.visible'); - // Analyze for accessibility - testA11y('ds-breadcrumbs'); - }); + // Analyze for accessibility + testA11y('ds-breadcrumbs'); + }); }); diff --git a/cypress/e2e/browse-by-author.cy.ts b/cypress/e2e/browse-by-author.cy.ts index 32c470231d7..3e914a2f8c0 100644 --- a/cypress/e2e/browse-by-author.cy.ts +++ b/cypress/e2e/browse-by-author.cy.ts @@ -1,13 +1,13 @@ import { testA11y } from 'cypress/support/utils'; describe('Browse By Author', () => { - it('should pass accessibility tests', () => { - cy.visit('/browse/author'); + it('should pass accessibility tests', () => { + cy.visit('/browse/author'); - // Wait for to be visible - cy.get('ds-browse-by-metadata').should('be.visible'); + // Wait for to be visible + cy.get('ds-browse-by-metadata').should('be.visible'); - // Analyze for accessibility - testA11y('ds-browse-by-metadata'); - }); + // Analyze for accessibility + testA11y('ds-browse-by-metadata'); + }); }); diff --git a/cypress/e2e/browse-by-dateissued.cy.ts b/cypress/e2e/browse-by-dateissued.cy.ts index 7966f1c82eb..5fe05433153 100644 --- a/cypress/e2e/browse-by-dateissued.cy.ts +++ b/cypress/e2e/browse-by-dateissued.cy.ts @@ -1,13 +1,13 @@ import { testA11y } from 'cypress/support/utils'; describe('Browse By Date Issued', () => { - it('should pass accessibility tests', () => { - cy.visit('/browse/dateissued'); + it('should pass accessibility tests', () => { + cy.visit('/browse/dateissued'); - // Wait for to be visible - cy.get('ds-browse-by-date').should('be.visible'); + // Wait for to be visible + cy.get('ds-browse-by-date').should('be.visible'); - // Analyze for accessibility - testA11y('ds-browse-by-date'); - }); + // Analyze for accessibility + testA11y('ds-browse-by-date'); + }); }); diff --git a/cypress/e2e/browse-by-subject.cy.ts b/cypress/e2e/browse-by-subject.cy.ts index 57ca88d1032..0937a2542bb 100644 --- a/cypress/e2e/browse-by-subject.cy.ts +++ b/cypress/e2e/browse-by-subject.cy.ts @@ -1,13 +1,13 @@ import { testA11y } from 'cypress/support/utils'; describe('Browse By Subject', () => { - it('should pass accessibility tests', () => { - cy.visit('/browse/subject'); + it('should pass accessibility tests', () => { + cy.visit('/browse/subject'); - // Wait for to be visible - cy.get('ds-browse-by-metadata').should('be.visible'); + // Wait for to be visible + cy.get('ds-browse-by-metadata').should('be.visible'); - // Analyze for accessibility - testA11y('ds-browse-by-metadata'); - }); + // Analyze for accessibility + testA11y('ds-browse-by-metadata'); + }); }); diff --git a/cypress/e2e/browse-by-title.cy.ts b/cypress/e2e/browse-by-title.cy.ts index 09195c30df2..71a7356ce32 100644 --- a/cypress/e2e/browse-by-title.cy.ts +++ b/cypress/e2e/browse-by-title.cy.ts @@ -1,13 +1,13 @@ import { testA11y } from 'cypress/support/utils'; describe('Browse By Title', () => { - it('should pass accessibility tests', () => { - cy.visit('/browse/title'); + it('should pass accessibility tests', () => { + cy.visit('/browse/title'); - // Wait for to be visible - cy.get('ds-browse-by-title').should('be.visible'); + // Wait for to be visible + cy.get('ds-browse-by-title').should('be.visible'); - // Analyze for accessibility - testA11y('ds-browse-by-title'); - }); + // Analyze for accessibility + testA11y('ds-browse-by-title'); + }); }); diff --git a/cypress/e2e/collection-edit.cy.ts b/cypress/e2e/collection-edit.cy.ts index 63d873db3ec..e1ba1c5eed8 100644 --- a/cypress/e2e/collection-edit.cy.ts +++ b/cypress/e2e/collection-edit.cy.ts @@ -3,126 +3,126 @@ import { testA11y } from 'cypress/support/utils'; const COLLECTION_EDIT_PAGE = '/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('/edit'); beforeEach(() => { - // All tests start with visiting the Edit Collection Page - cy.visit(COLLECTION_EDIT_PAGE); + // All tests start with visiting the Edit Collection Page + cy.visit(COLLECTION_EDIT_PAGE); - // 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')); + // 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')); }); describe('Edit Collection > Edit Metadata tab', () => { - it('should pass accessibility tests', () => { - // tag must be loaded - cy.get('ds-edit-collection').should('be.visible'); + it('should pass accessibility tests', () => { + // tag must be loaded + cy.get('ds-edit-collection').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-edit-collection'); - }); + // Analyze for accessibility issues + testA11y('ds-edit-collection'); + }); }); describe('Edit Collection > Assign Roles tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="roles"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="roles"]').click(); - // tag must be loaded - cy.get('ds-collection-roles').should('be.visible'); + // tag must be loaded + cy.get('ds-collection-roles').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-collection-roles'); - }); + // Analyze for accessibility issues + testA11y('ds-collection-roles'); + }); }); describe('Edit Collection > Content Source tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="source"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="source"]').click(); - // tag must be loaded - cy.get('ds-collection-source').should('be.visible'); + // tag must be loaded + cy.get('ds-collection-source').should('be.visible'); - // Check the external source checkbox (to display all fields on the page) - cy.get('#externalSourceCheck').check(); + // Check the external source checkbox (to display all fields on the page) + cy.get('#externalSourceCheck').check(); - // Wait for the source controls to appear - // cy.get('ds-collection-source-controls').should('be.visible'); + // Wait for the source controls to appear + // cy.get('ds-collection-source-controls').should('be.visible'); - // Analyze entire page for accessibility issues - testA11y('ds-collection-source'); - }); + // Analyze entire page for accessibility issues + testA11y('ds-collection-source'); + }); }); describe('Edit Collection > Curate tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="curate"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="curate"]').click(); - // tag must be loaded - cy.get('ds-collection-curate').should('be.visible'); + // tag must be loaded + cy.get('ds-collection-curate').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-collection-curate'); - }); + // Analyze for accessibility issues + testA11y('ds-collection-curate'); + }); }); describe('Edit Collection > Access Control tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="access-control"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="access-control"]').click(); - // tag must be loaded - cy.get('ds-collection-access-control').should('be.visible'); + // tag must be loaded + cy.get('ds-collection-access-control').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-collection-access-control'); - }); + // Analyze for accessibility issues + testA11y('ds-collection-access-control'); + }); }); describe('Edit Collection > Authorizations tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="authorizations"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="authorizations"]').click(); - // tag must be loaded - cy.get('ds-collection-authorizations').should('be.visible'); + // tag must be loaded + cy.get('ds-collection-authorizations').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-collection-authorizations'); - }); + // Analyze for accessibility issues + testA11y('ds-collection-authorizations'); + }); }); describe('Edit Collection > Item Mapper tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="mapper"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="mapper"]').click(); - // tag must be loaded - cy.get('ds-collection-item-mapper').should('be.visible'); + // tag must be loaded + cy.get('ds-collection-item-mapper').should('be.visible'); - // Analyze entire page for accessibility issues - testA11y('ds-collection-item-mapper'); + // Analyze entire page for accessibility issues + testA11y('ds-collection-item-mapper'); - // Click on the "Map new Items" tab - cy.get('li[data-test="mapTab"] a').click(); + // Click on the "Map new Items" tab + cy.get('li[data-test="mapTab"] a').click(); - // Make sure search form is now visible - cy.get('ds-search-form').should('be.visible'); + // Make sure search form is now visible + cy.get('ds-search-form').should('be.visible'); - // Analyze entire page (again) for accessibility issues - testA11y('ds-collection-item-mapper'); - }); + // Analyze entire page (again) for accessibility issues + testA11y('ds-collection-item-mapper'); + }); }); describe('Edit Collection > Delete page', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="delete-button"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="delete-button"]').click(); - // tag must be loaded - cy.get('ds-delete-collection').should('be.visible'); + // tag must be loaded + cy.get('ds-delete-collection').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-delete-collection'); - }); + // Analyze for accessibility issues + testA11y('ds-delete-collection'); + }); }); diff --git a/cypress/e2e/collection-page.cy.ts b/cypress/e2e/collection-page.cy.ts index 55c10cc6e22..d12536d332a 100644 --- a/cypress/e2e/collection-page.cy.ts +++ b/cypress/e2e/collection-page.cy.ts @@ -2,13 +2,13 @@ import { testA11y } from 'cypress/support/utils'; describe('Collection Page', () => { - it('should pass accessibility tests', () => { - cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION'))); + it('should pass accessibility tests', () => { + cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION'))); - // tag must be loaded - cy.get('ds-collection-page').should('be.visible'); + // tag must be loaded + cy.get('ds-collection-page').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-collection-page'); - }); + // Analyze for accessibility issues + testA11y('ds-collection-page'); + }); }); diff --git a/cypress/e2e/collection-statistics.cy.ts b/cypress/e2e/collection-statistics.cy.ts index a08f8cb1987..3e5a465e398 100644 --- a/cypress/e2e/collection-statistics.cy.ts +++ b/cypress/e2e/collection-statistics.cy.ts @@ -2,36 +2,36 @@ import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Collection Statistics Page', () => { - const COLLECTIONSTATISTICSPAGE = '/statistics/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')); - - it('should load if you click on "Statistics" from a Collection page', () => { - cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION'))); - cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click(); - cy.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE); - }); - - it('should contain a "Total visits" section', () => { - cy.visit(COLLECTIONSTATISTICSPAGE); - cy.get('table[data-test="TotalVisits"]').should('be.visible'); - }); - - it('should contain a "Total visits per month" section', () => { - cy.visit(COLLECTIONSTATISTICSPAGE); - // Check just for existence because this table is empty in CI environment as it's historical data - cy.get('.'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('_TotalVisitsPerMonth')).should('exist'); - }); - - it('should pass accessibility tests', () => { - cy.visit(COLLECTIONSTATISTICSPAGE); - - // tag must be loaded - cy.get('ds-collection-statistics-page').should('be.visible'); - - // Verify / wait until "Total Visits" table's label is non-empty - // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) - cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); - - // Analyze for accessibility issues - testA11y('ds-collection-statistics-page'); - }); + const COLLECTIONSTATISTICSPAGE = '/statistics/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')); + + it('should load if you click on "Statistics" from a Collection page', () => { + cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION'))); + cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click(); + cy.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE); + }); + + it('should contain a "Total visits" section', () => { + cy.visit(COLLECTIONSTATISTICSPAGE); + cy.get('table[data-test="TotalVisits"]').should('be.visible'); + }); + + it('should contain a "Total visits per month" section', () => { + cy.visit(COLLECTIONSTATISTICSPAGE); + // Check just for existence because this table is empty in CI environment as it's historical data + cy.get('.'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('_TotalVisitsPerMonth')).should('exist'); + }); + + it('should pass accessibility tests', () => { + cy.visit(COLLECTIONSTATISTICSPAGE); + + // tag must be loaded + cy.get('ds-collection-statistics-page').should('be.visible'); + + // Verify / wait until "Total Visits" table's label is non-empty + // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) + cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); + + // Analyze for accessibility issues + testA11y('ds-collection-statistics-page'); + }); }); diff --git a/cypress/e2e/community-edit.cy.ts b/cypress/e2e/community-edit.cy.ts index 8fc1a7733e7..77e260feec0 100644 --- a/cypress/e2e/community-edit.cy.ts +++ b/cypress/e2e/community-edit.cy.ts @@ -3,84 +3,84 @@ import { testA11y } from 'cypress/support/utils'; const COMMUNITY_EDIT_PAGE = '/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')).concat('/edit'); beforeEach(() => { - // All tests start with visiting the Edit Community Page - cy.visit(COMMUNITY_EDIT_PAGE); + // All tests start with visiting the Edit Community Page + cy.visit(COMMUNITY_EDIT_PAGE); - // 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')); + // 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')); }); describe('Edit Community > Edit Metadata tab', () => { - it('should pass accessibility tests', () => { - // tag must be loaded - cy.get('ds-edit-community').should('be.visible'); + it('should pass accessibility tests', () => { + // tag must be loaded + cy.get('ds-edit-community').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-edit-community'); - }); + // Analyze for accessibility issues + testA11y('ds-edit-community'); + }); }); describe('Edit Community > Assign Roles tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="roles"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="roles"]').click(); - // tag must be loaded - cy.get('ds-community-roles').should('be.visible'); + // tag must be loaded + cy.get('ds-community-roles').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-community-roles'); - }); + // Analyze for accessibility issues + testA11y('ds-community-roles'); + }); }); describe('Edit Community > Curate tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="curate"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="curate"]').click(); - // tag must be loaded - cy.get('ds-community-curate').should('be.visible'); + // tag must be loaded + cy.get('ds-community-curate').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-community-curate'); - }); + // Analyze for accessibility issues + testA11y('ds-community-curate'); + }); }); describe('Edit Community > Access Control tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="access-control"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="access-control"]').click(); - // tag must be loaded - cy.get('ds-community-access-control').should('be.visible'); + // tag must be loaded + cy.get('ds-community-access-control').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-community-access-control'); - }); + // Analyze for accessibility issues + testA11y('ds-community-access-control'); + }); }); describe('Edit Community > Authorizations tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="authorizations"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="authorizations"]').click(); - // tag must be loaded - cy.get('ds-community-authorizations').should('be.visible'); + // tag must be loaded + cy.get('ds-community-authorizations').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-community-authorizations'); - }); + // Analyze for accessibility issues + testA11y('ds-community-authorizations'); + }); }); describe('Edit Community > Delete page', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="delete-button"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="delete-button"]').click(); - // tag must be loaded - cy.get('ds-delete-community').should('be.visible'); + // tag must be loaded + cy.get('ds-delete-community').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-delete-community'); - }); + // Analyze for accessibility issues + testA11y('ds-delete-community'); + }); }); diff --git a/cypress/e2e/community-list.cy.ts b/cypress/e2e/community-list.cy.ts index c371f6ceae7..9b9c87b112d 100644 --- a/cypress/e2e/community-list.cy.ts +++ b/cypress/e2e/community-list.cy.ts @@ -2,16 +2,16 @@ import { testA11y } from 'cypress/support/utils'; describe('Community List Page', () => { - it('should pass accessibility tests', () => { - cy.visit('/community-list'); + it('should pass accessibility tests', () => { + cy.visit('/community-list'); - // tag must be loaded - cy.get('ds-community-list-page').should('be.visible'); + // tag must be loaded + cy.get('ds-community-list-page').should('be.visible'); - // Open every expand button on page, so that we can scan sub-elements as well - cy.get('[data-test="expand-button"]').click({ multiple: true }); + // Open every expand button on page, so that we can scan sub-elements as well + cy.get('[data-test="expand-button"]').click({ multiple: true }); - // Analyze for accessibility issues - testA11y('ds-community-list-page'); - }); + // Analyze for accessibility issues + testA11y('ds-community-list-page'); + }); }); diff --git a/cypress/e2e/community-page.cy.ts b/cypress/e2e/community-page.cy.ts index 386bb592a0a..5a4441dbae8 100644 --- a/cypress/e2e/community-page.cy.ts +++ b/cypress/e2e/community-page.cy.ts @@ -2,13 +2,13 @@ import { testA11y } from 'cypress/support/utils'; describe('Community Page', () => { - it('should pass accessibility tests', () => { - cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY'))); + it('should pass accessibility tests', () => { + cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY'))); - // tag must be loaded - cy.get('ds-community-page').should('be.visible'); + // tag must be loaded + cy.get('ds-community-page').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-community-page'); - }); + // Analyze for accessibility issues + testA11y('ds-community-page'); + }); }); diff --git a/cypress/e2e/community-statistics.cy.ts b/cypress/e2e/community-statistics.cy.ts index 6cafed0350e..00e23a90b37 100644 --- a/cypress/e2e/community-statistics.cy.ts +++ b/cypress/e2e/community-statistics.cy.ts @@ -2,36 +2,36 @@ import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Community Statistics Page', () => { - const COMMUNITYSTATISTICSPAGE = '/statistics/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')); - - it('should load if you click on "Statistics" from a Community page', () => { - cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY'))); - cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click(); - cy.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE); - }); - - it('should contain a "Total visits" section', () => { - cy.visit(COMMUNITYSTATISTICSPAGE); - cy.get('table[data-test="TotalVisits"]').should('be.visible'); - }); - - it('should contain a "Total visits per month" section', () => { - cy.visit(COMMUNITYSTATISTICSPAGE); - // Check just for existence because this table is empty in CI environment as it's historical data - cy.get('.'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')).concat('_TotalVisitsPerMonth')).should('exist'); - }); - - it('should pass accessibility tests', () => { - cy.visit(COMMUNITYSTATISTICSPAGE); - - // tag must be loaded - cy.get('ds-community-statistics-page').should('be.visible'); - - // Verify / wait until "Total Visits" table's label is non-empty - // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) - cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); - - // Analyze for accessibility issues - testA11y('ds-community-statistics-page'); - }); + const COMMUNITYSTATISTICSPAGE = '/statistics/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')); + + it('should load if you click on "Statistics" from a Community page', () => { + cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY'))); + cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click(); + cy.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE); + }); + + it('should contain a "Total visits" section', () => { + cy.visit(COMMUNITYSTATISTICSPAGE); + cy.get('table[data-test="TotalVisits"]').should('be.visible'); + }); + + it('should contain a "Total visits per month" section', () => { + cy.visit(COMMUNITYSTATISTICSPAGE); + // Check just for existence because this table is empty in CI environment as it's historical data + cy.get('.'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')).concat('_TotalVisitsPerMonth')).should('exist'); + }); + + it('should pass accessibility tests', () => { + cy.visit(COMMUNITYSTATISTICSPAGE); + + // tag must be loaded + cy.get('ds-community-statistics-page').should('be.visible'); + + // Verify / wait until "Total Visits" table's label is non-empty + // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) + cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); + + // Analyze for accessibility issues + testA11y('ds-community-statistics-page'); + }); }); diff --git a/cypress/e2e/footer.cy.ts b/cypress/e2e/footer.cy.ts index 656e9d47012..4ee1d6669ae 100644 --- a/cypress/e2e/footer.cy.ts +++ b/cypress/e2e/footer.cy.ts @@ -1,13 +1,13 @@ import { testA11y } from 'cypress/support/utils'; describe('Footer', () => { - it('should pass accessibility tests', () => { - cy.visit('/'); + it('should pass accessibility tests', () => { + cy.visit('/'); - // Footer must first be visible - cy.get('ds-footer').should('be.visible'); + // Footer must first be visible + cy.get('ds-footer').should('be.visible'); - // Analyze for accessibility - testA11y('ds-footer'); - }); + // Analyze for accessibility + testA11y('ds-footer'); + }); }); diff --git a/cypress/e2e/header.cy.ts b/cypress/e2e/header.cy.ts index 9852216e438..043d67dd2b9 100644 --- a/cypress/e2e/header.cy.ts +++ b/cypress/e2e/header.cy.ts @@ -1,13 +1,13 @@ import { testA11y } from 'cypress/support/utils'; describe('Header', () => { - it('should pass accessibility tests', () => { - cy.visit('/'); + it('should pass accessibility tests', () => { + cy.visit('/'); - // Header must first be visible - cy.get('ds-header').should('be.visible'); + // Header must first be visible + cy.get('ds-header').should('be.visible'); - // Analyze for accessibility - testA11y('ds-header'); - }); + // Analyze for accessibility + testA11y('ds-header'); + }); }); diff --git a/cypress/e2e/homepage-statistics.cy.ts b/cypress/e2e/homepage-statistics.cy.ts index ece38686b93..f9642c0c831 100644 --- a/cypress/e2e/homepage-statistics.cy.ts +++ b/cypress/e2e/homepage-statistics.cy.ts @@ -1,31 +1,32 @@ +import '../support/commands'; + import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; -import '../support/commands'; describe('Site Statistics Page', () => { - it('should load if you click on "Statistics" from homepage', () => { - cy.visit('/'); - cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click(); - cy.location('pathname').should('eq', '/statistics'); - }); + it('should load if you click on "Statistics" from homepage', () => { + cy.visit('/'); + cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click(); + cy.location('pathname').should('eq', '/statistics'); + }); - it('should pass accessibility tests', () => { - // generate 2 view events on an Item's page - cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item'); - cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item'); + it('should pass accessibility tests', () => { + // generate 2 view events on an Item's page + cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item'); + cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item'); - cy.visit('/statistics'); + cy.visit('/statistics'); - // tag must be visable - cy.get('ds-site-statistics-page').should('be.visible'); + // tag must be visable + cy.get('ds-site-statistics-page').should('be.visible'); - // Verify / wait until "Total Visits" table's *last* label is non-empty - // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) - cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').last().contains(REGEX_MATCH_NON_EMPTY_TEXT); - // Wait an extra 500ms, just so all entries in Total Visits have loaded. - cy.wait(500); + // Verify / wait until "Total Visits" table's *last* label is non-empty + // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) + cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').last().contains(REGEX_MATCH_NON_EMPTY_TEXT); + // Wait an extra 500ms, just so all entries in Total Visits have loaded. + cy.wait(500); - // Analyze for accessibility issues - testA11y('ds-site-statistics-page'); - }); + // Analyze for accessibility issues + testA11y('ds-site-statistics-page'); + }); }); diff --git a/cypress/e2e/item-edit.cy.ts b/cypress/e2e/item-edit.cy.ts index b4c01a1a946..b13d5a46958 100644 --- a/cypress/e2e/item-edit.cy.ts +++ b/cypress/e2e/item-edit.cy.ts @@ -1,135 +1,135 @@ -import { Options } from 'cypress-axe'; import { testA11y } from 'cypress/support/utils'; +import { Options } from 'cypress-axe'; const ITEM_EDIT_PAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')).concat('/edit'); beforeEach(() => { - // All tests start with visiting the Edit Item Page - cy.visit(ITEM_EDIT_PAGE); + // All tests start with visiting the Edit Item Page + cy.visit(ITEM_EDIT_PAGE); - // 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')); + // 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')); }); describe('Edit Item > Edit Metadata tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="metadata"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="metadata"]').click(); - // tag must be loaded - cy.get('ds-edit-item-page').should('be.visible'); + // tag must be loaded + cy.get('ds-edit-item-page').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-edit-item-page'); - }); + // Analyze for accessibility issues + testA11y('ds-edit-item-page'); + }); }); describe('Edit Item > Status tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="status"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="status"]').click(); - // tag must be loaded - cy.get('ds-item-status').should('be.visible'); + // tag must be loaded + cy.get('ds-item-status').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-item-status'); - }); + // Analyze for accessibility issues + testA11y('ds-item-status'); + }); }); describe('Edit Item > Bitstreams tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="bitstreams"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="bitstreams"]').click(); - // tag must be loaded - cy.get('ds-item-bitstreams').should('be.visible'); + // tag must be loaded + cy.get('ds-item-bitstreams').should('be.visible'); - // Table of item bitstreams must also be loaded - cy.get('div.item-bitstreams').should('be.visible'); + // Table of item bitstreams must also be loaded + cy.get('div.item-bitstreams').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-item-bitstreams', + // Analyze for accessibility issues + testA11y('ds-item-bitstreams', { - rules: { - // Currently Bitstreams page loads a pagination component per Bundle - // and they all use the same 'id="p-dad"'. - 'duplicate-id': { enabled: false }, - } - } as Options - ); - }); + rules: { + // Currently Bitstreams page loads a pagination component per Bundle + // and they all use the same 'id="p-dad"'. + 'duplicate-id': { enabled: false }, + }, + } as Options, + ); + }); }); describe('Edit Item > Curate tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="curate"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="curate"]').click(); - // tag must be loaded - cy.get('ds-item-curate').should('be.visible'); + // tag must be loaded + cy.get('ds-item-curate').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-item-curate'); - }); + // Analyze for accessibility issues + testA11y('ds-item-curate'); + }); }); describe('Edit Item > Relationships tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="relationships"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="relationships"]').click(); - // tag must be loaded - cy.get('ds-item-relationships').should('be.visible'); + // tag must be loaded + cy.get('ds-item-relationships').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-item-relationships'); - }); + // Analyze for accessibility issues + testA11y('ds-item-relationships'); + }); }); describe('Edit Item > Version History tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="versionhistory"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="versionhistory"]').click(); - // tag must be loaded - cy.get('ds-item-version-history').should('be.visible'); + // tag must be loaded + cy.get('ds-item-version-history').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-item-version-history'); - }); + // Analyze for accessibility issues + testA11y('ds-item-version-history'); + }); }); describe('Edit Item > Access Control tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="access-control"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="access-control"]').click(); - // tag must be loaded - cy.get('ds-item-access-control').should('be.visible'); + // tag must be loaded + cy.get('ds-item-access-control').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-item-access-control'); - }); + // Analyze for accessibility issues + testA11y('ds-item-access-control'); + }); }); describe('Edit Item > Collection Mapper tab', () => { - it('should pass accessibility tests', () => { - cy.get('a[data-test="mapper"]').click(); + it('should pass accessibility tests', () => { + cy.get('a[data-test="mapper"]').click(); - // tag must be loaded - cy.get('ds-item-collection-mapper').should('be.visible'); + // tag must be loaded + cy.get('ds-item-collection-mapper').should('be.visible'); - // Analyze entire page for accessibility issues - testA11y('ds-item-collection-mapper'); + // Analyze entire page for accessibility issues + testA11y('ds-item-collection-mapper'); - // Click on the "Map new collections" tab - cy.get('li[data-test="mapTab"] a').click(); + // Click on the "Map new collections" tab + cy.get('li[data-test="mapTab"] a').click(); - // Make sure search form is now visible - cy.get('ds-search-form').should('be.visible'); + // Make sure search form is now visible + cy.get('ds-search-form').should('be.visible'); - // Analyze entire page (again) for accessibility issues - testA11y('ds-item-collection-mapper'); - }); + // Analyze entire page (again) for accessibility issues + testA11y('ds-item-collection-mapper'); + }); }); diff --git a/cypress/e2e/item-page.cy.ts b/cypress/e2e/item-page.cy.ts index a6a208e9f45..b79b6ac31d1 100644 --- a/cypress/e2e/item-page.cy.ts +++ b/cypress/e2e/item-page.cy.ts @@ -1,32 +1,32 @@ import { testA11y } from 'cypress/support/utils'; describe('Item Page', () => { - const ITEMPAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')); - const ENTITYPAGE = '/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')); + const ITEMPAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')); + const ENTITYPAGE = '/entities/publication/'.concat(Cypress.env('DSPACE_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); - }); + // 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); + it('should pass accessibility tests', () => { + cy.visit(ENTITYPAGE); - // tag must be loaded - cy.get('ds-item-page').should('be.visible'); + // tag must be loaded + cy.get('ds-item-page').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-item-page'); - }); + // Analyze for accessibility issues + testA11y('ds-item-page'); + }); - it('should pass accessibility tests on full item page', () => { - cy.visit(ENTITYPAGE + '/full'); + it('should pass accessibility tests on full item page', () => { + cy.visit(ENTITYPAGE + '/full'); - // tag must be loaded - cy.get('ds-full-item-page').should('be.visible'); + // tag must be loaded + cy.get('ds-full-item-page').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-full-item-page'); - }); + // Analyze for accessibility issues + testA11y('ds-full-item-page'); + }); }); diff --git a/cypress/e2e/item-statistics.cy.ts b/cypress/e2e/item-statistics.cy.ts index 6caeacae8e1..6518f595a90 100644 --- a/cypress/e2e/item-statistics.cy.ts +++ b/cypress/e2e/item-statistics.cy.ts @@ -2,42 +2,42 @@ import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; describe('Item Statistics Page', () => { - const ITEMSTATISTICSPAGE = '/statistics/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')); - - it('should load if you click on "Statistics" from an Item/Entity page', () => { - cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'))); - cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.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); - cy.get('ds-item-statistics-page').should('be.visible'); - cy.get('ds-item-page').should('not.exist'); - }); - - it('should contain a "Total visits" section', () => { - cy.visit(ITEMSTATISTICSPAGE); - cy.get('table[data-test="TotalVisits"]').should('be.visible'); - }); - - it('should contain a "Total visits per month" section', () => { - cy.visit(ITEMSTATISTICSPAGE); - // Check just for existence because this table is empty in CI environment as it's historical data - cy.get('.'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')).concat('_TotalVisitsPerMonth')).should('exist'); - }); - - it('should pass accessibility tests', () => { - cy.visit(ITEMSTATISTICSPAGE); - - // tag must be loaded - cy.get('ds-item-statistics-page').should('be.visible'); - - // Verify / wait until "Total Visits" table's label is non-empty - // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) - cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); - - // Analyze for accessibility issues - testA11y('ds-item-statistics-page'); - }); + const ITEMSTATISTICSPAGE = '/statistics/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')); + + it('should load if you click on "Statistics" from an Item/Entity page', () => { + cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'))); + cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.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); + cy.get('ds-item-statistics-page').should('be.visible'); + cy.get('ds-item-page').should('not.exist'); + }); + + it('should contain a "Total visits" section', () => { + cy.visit(ITEMSTATISTICSPAGE); + cy.get('table[data-test="TotalVisits"]').should('be.visible'); + }); + + it('should contain a "Total visits per month" section', () => { + cy.visit(ITEMSTATISTICSPAGE); + // Check just for existence because this table is empty in CI environment as it's historical data + cy.get('.'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')).concat('_TotalVisitsPerMonth')).should('exist'); + }); + + it('should pass accessibility tests', () => { + cy.visit(ITEMSTATISTICSPAGE); + + // tag must be loaded + cy.get('ds-item-statistics-page').should('be.visible'); + + // Verify / wait until "Total Visits" table's label is non-empty + // (This table loads these labels asynchronously, so we want to wait for them before analyzing page) + cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT); + + // Analyze for accessibility issues + testA11y('ds-item-statistics-page'); + }); }); diff --git a/cypress/e2e/login-modal.cy.ts b/cypress/e2e/login-modal.cy.ts index 673041e9f39..190f3ff9271 100644 --- a/cypress/e2e/login-modal.cy.ts +++ b/cypress/e2e/login-modal.cy.ts @@ -1,150 +1,150 @@ import { testA11y } from 'cypress/support/utils'; const page = { - openLoginMenu() { - // Click the "Log In" dropdown menu in header - cy.get('ds-themed-header [data-test="login-menu"]').click(); - }, - openUserMenu() { - // Once logged in, click the User menu in header - cy.get('ds-themed-header [data-test="user-menu"]').click(); - }, - submitLoginAndPasswordByPressingButton(email, password) { - // Enter email - cy.get('ds-themed-header [data-test="email"]').type(email); - // Enter password - cy.get('ds-themed-header [data-test="password"]').type(password); - // Click login button - cy.get('ds-themed-header [data-test="login-button"]').click(); - }, - submitLoginAndPasswordByPressingEnter(email, password) { - // In opened Login modal, fill out email & password, then click Enter - cy.get('ds-themed-header [data-test="email"]').type(email); - cy.get('ds-themed-header [data-test="password"]').type(password); - cy.get('ds-themed-header [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-themed-header [data-test="logout-button"]').click(); - // Wait until above POST command responds before continuing - // (This ensures next action waits until logout completes) - cy.wait('@logout'); - } + openLoginMenu() { + // Click the "Log In" dropdown menu in header + cy.get('ds-themed-header [data-test="login-menu"]').click(); + }, + openUserMenu() { + // Once logged in, click the User menu in header + cy.get('ds-themed-header [data-test="user-menu"]').click(); + }, + submitLoginAndPasswordByPressingButton(email, password) { + // Enter email + cy.get('ds-themed-header [data-test="email"]').type(email); + // Enter password + cy.get('ds-themed-header [data-test="password"]').type(password); + // Click login button + cy.get('ds-themed-header [data-test="login-button"]').click(); + }, + submitLoginAndPasswordByPressingEnter(email, password) { + // In opened Login modal, fill out email & password, then click Enter + cy.get('ds-themed-header [data-test="email"]').type(email); + cy.get('ds-themed-header [data-test="password"]').type(password); + cy.get('ds-themed-header [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-themed-header [data-test="logout-button"]').click(); + // Wait until above POST command responds before continuing + // (This ensures next action waits until logout completes) + cy.wait('@logout'); + }, }; describe('Login Modal', () => { - it('should login when clicking button & stay on same page', () => { - const ENTITYPAGE = '/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')); - cy.visit(ENTITYPAGE); + it('should login when clicking button & stay on same page', () => { + const ENTITYPAGE = '/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')); + cy.visit(ENTITYPAGE); - // Login menu should exist - cy.get('ds-log-in').should('exist'); + // Login menu should exist + cy.get('ds-log-in').should('exist'); - // Login, and the tag should no longer exist - page.openLoginMenu(); - cy.get('.form-login').should('be.visible'); + // Login, and the tag should no longer exist + page.openLoginMenu(); + cy.get('.form-login').should('be.visible'); - page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); - cy.get('ds-log-in').should('not.exist'); + page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); + cy.get('ds-log-in').should('not.exist'); - // Verify we are still on the same page - cy.url().should('include', ENTITYPAGE); + // Verify we are still on the same page + cy.url().should('include', ENTITYPAGE); - // Open user menu, verify user menu & logout button now available - page.openUserMenu(); - cy.get('ds-user-menu').should('be.visible'); - cy.get('ds-log-out').should('be.visible'); - }); + // Open user menu, verify user menu & logout button now available + page.openUserMenu(); + cy.get('ds-user-menu').should('be.visible'); + cy.get('ds-log-out').should('be.visible'); + }); - it('should login when clicking enter key & stay on same page', () => { - cy.visit('/home'); + it('should login when clicking enter key & stay on same page', () => { + cy.visit('/home'); - // Open login menu in header & verify tag is visible - page.openLoginMenu(); - cy.get('.form-login').should('be.visible'); + // Open login menu in header & verify tag is visible + page.openLoginMenu(); + cy.get('.form-login').should('be.visible'); - // Login, and the 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'); + // Login, and the 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'); - // Verify we are still on homepage - cy.url().should('include', '/home'); + // Verify we are still on homepage + cy.url().should('include', '/home'); - // Open user menu, verify user menu & logout button now available - page.openUserMenu(); - cy.get('ds-user-menu').should('be.visible'); - cy.get('ds-log-out').should('be.visible'); - }); + // Open user menu, verify user menu & logout button now available + page.openUserMenu(); + cy.get('ds-user-menu').should('be.visible'); + cy.get('ds-log-out').should('be.visible'); + }); - it('should support logout', () => { - // First authenticate & access homepage - cy.login(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); - cy.visit('/'); + it('should support logout', () => { + // First authenticate & access homepage + cy.login(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); + cy.visit('/'); - // Verify ds-log-in tag doesn't exist, but ds-log-out tag does exist - cy.get('ds-log-in').should('not.exist'); - cy.get('ds-log-out').should('exist'); + // Verify ds-log-in tag doesn't exist, but ds-log-out tag does exist + cy.get('ds-log-in').should('not.exist'); + cy.get('ds-log-out').should('exist'); - // Click logout button - page.openUserMenu(); - page.submitLogoutByPressingButton(); + // Click logout button + page.openUserMenu(); + page.submitLogoutByPressingButton(); - // Verify ds-log-in tag now exists - cy.get('ds-log-in').should('exist'); - cy.get('ds-log-out').should('not.exist'); - }); + // Verify ds-log-in tag now exists + cy.get('ds-log-in').should('exist'); + cy.get('ds-log-out').should('not.exist'); + }); - it('should allow new user registration', () => { - cy.visit('/'); + it('should allow new user registration', () => { + cy.visit('/'); - page.openLoginMenu(); + page.openLoginMenu(); - // Registration link should be visible - cy.get('ds-themed-header [data-test="register"]').should('be.visible'); + // Registration link should be visible + cy.get('ds-themed-header [data-test="register"]').should('be.visible'); - // Click registration link & you should go to registration page - cy.get('ds-themed-header [data-test="register"]').click(); - cy.location('pathname').should('eq', '/register'); - cy.get('ds-register-email').should('exist'); + // Click registration link & you should go to registration page + cy.get('ds-themed-header [data-test="register"]').click(); + cy.location('pathname').should('eq', '/register'); + cy.get('ds-register-email').should('exist'); - // Test accessibility of this page - testA11y('ds-register-email'); - }); + // Test accessibility of this page + testA11y('ds-register-email'); + }); - it('should allow forgot password', () => { - cy.visit('/'); + it('should allow forgot password', () => { + cy.visit('/'); - page.openLoginMenu(); + page.openLoginMenu(); - // Forgot password link should be visible - cy.get('ds-themed-header [data-test="forgot"]').should('be.visible'); + // Forgot password link should be visible + cy.get('ds-themed-header [data-test="forgot"]').should('be.visible'); - // Click link & you should go to Forgot Password page - cy.get('ds-themed-header [data-test="forgot"]').click(); - cy.location('pathname').should('eq', '/forgot'); - cy.get('ds-forgot-email').should('exist'); + // Click link & you should go to Forgot Password page + cy.get('ds-themed-header [data-test="forgot"]').click(); + cy.location('pathname').should('eq', '/forgot'); + cy.get('ds-forgot-email').should('exist'); - // Test accessibility of this page - testA11y('ds-forgot-email'); - }); + // Test accessibility of this page + testA11y('ds-forgot-email'); + }); - it('should pass accessibility tests in menus', () => { - cy.visit('/'); + it('should pass accessibility tests in menus', () => { + cy.visit('/'); - // Open login menu & verify accessibility - page.openLoginMenu(); - cy.get('ds-log-in').should('exist'); - testA11y('ds-log-in'); + // Open login menu & verify accessibility + page.openLoginMenu(); + cy.get('ds-log-in').should('exist'); + testA11y('ds-log-in'); - // Now login - page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); - cy.get('ds-log-in').should('not.exist'); + // Now login + page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); + cy.get('ds-log-in').should('not.exist'); - // Open user menu, verify user menu accesibility - page.openUserMenu(); - cy.get('ds-user-menu').should('be.visible'); - testA11y('ds-user-menu'); - }); + // Open user menu, verify user menu accesibility + page.openUserMenu(); + cy.get('ds-user-menu').should('be.visible'); + testA11y('ds-user-menu'); + }); }); diff --git a/cypress/e2e/my-dspace.cy.ts b/cypress/e2e/my-dspace.cy.ts index c48656ffcc0..159bb4f5e65 100644 --- a/cypress/e2e/my-dspace.cy.ts +++ b/cypress/e2e/my-dspace.cy.ts @@ -1,134 +1,134 @@ import { testA11y } from 'cypress/support/utils'; describe('My DSpace page', () => { - it('should display recent submissions and pass accessibility tests', () => { - cy.visit('/mydspace'); + it('should display recent submissions and pass accessibility tests', () => { + cy.visit('/mydspace'); - // This page is restricted, so we will be shown the login form. Fill it out & submit. - cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); + // This page is restricted, so we will be shown the login form. Fill it out & submit. + cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); - cy.get('ds-my-dspace-page').should('be.visible'); + cy.get('ds-my-dspace-page').should('be.visible'); - // At least one recent submission should be displayed - cy.get('[data-test="list-object"]').should('be.visible'); + // At least one recent submission 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('.filter-toggle').click({ multiple: true }); + // 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 for accessibility issues - testA11y('ds-my-dspace-page'); - }); + // Analyze for accessibility issues + testA11y('ds-my-dspace-page'); + }); - it('should have a working detailed view that passes accessibility tests', () => { - cy.visit('/mydspace'); + it('should have a working detailed view that passes accessibility tests', () => { + cy.visit('/mydspace'); - // This page is restricted, so we will be shown the login form. Fill it out & submit. - cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); + // This page is restricted, so we will be shown the login form. Fill it out & submit. + cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); - cy.get('ds-my-dspace-page').should('be.visible'); + cy.get('ds-my-dspace-page').should('be.visible'); - // Click button in sidebar to display detailed view - cy.get('ds-search-sidebar [data-test="detail-view"]').click(); + // Click button in sidebar to display detailed view + cy.get('ds-search-sidebar [data-test="detail-view"]').click(); - cy.get('ds-object-detail').should('be.visible'); + cy.get('ds-object-detail').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-my-dspace-page'); - }); + // Analyze for accessibility issues + testA11y('ds-my-dspace-page'); + }); - // NOTE: Deleting existing submissions is exercised by submission.spec.ts - it('should let you start a new submission & edit in-progress submissions', () => { - cy.visit('/mydspace'); + // NOTE: Deleting existing submissions is exercised by submission.spec.ts + it('should let you start a new submission & edit in-progress submissions', () => { + cy.visit('/mydspace'); - // This page is restricted, so we will be shown the login form. Fill it out & submit. - cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); + // This page is restricted, so we will be shown the login form. Fill it out & submit. + cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); - // Open the New Submission dropdown - cy.get('button[data-test="submission-dropdown"]').click(); - // Click on the "Item" type in that dropdown - cy.get('#entityControlsDropdownMenu button[title="none"]').click(); + // Open the New Submission dropdown + cy.get('button[data-test="submission-dropdown"]').click(); + // Click on the "Item" type in that dropdown + cy.get('#entityControlsDropdownMenu button[title="none"]').click(); - // This should display the (popup window) - cy.get('ds-create-item-parent-selector').should('be.visible'); + // This should display the (popup window) + cy.get('ds-create-item-parent-selector').should('be.visible'); - // Type in a known Collection name in the search box - cy.get('ds-authorized-collection-selector input[type="search"]').type(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')); + // Type in a known Collection name in the search box + cy.get('ds-authorized-collection-selector input[type="search"]').type(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')); - // Click on the button matching that known Collection name - cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')).concat('"]')).click(); + // Click on the button matching that known Collection name + cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')).concat('"]')).click(); - // New URL should include /workspaceitems, as we've started a new submission - cy.url().should('include', '/workspaceitems'); + // New URL should include /workspaceitems, as we've started a new submission + cy.url().should('include', '/workspaceitems'); - // The Submission edit form tag should be visible - cy.get('ds-submission-edit').should('be.visible'); + // The Submission edit form tag should be visible + cy.get('ds-submission-edit').should('be.visible'); - // A Collection menu button should exist & its value should be the selected collection - cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')); + // A Collection menu button should exist & its value should be the selected collection + cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')); - // Now that we've created a submission, we'll test that we can go back and Edit it. - // Get our Submission URL, to parse out the ID of this new submission - cy.location().then(fullUrl => { - // This will be the full path (/workspaceitems/[id]/edit) - const path = fullUrl.pathname; - // Split on the slashes - const subpaths = path.split('/'); - // Part 2 will be the [id] of the submission - const id = subpaths[2]; + // Now that we've created a submission, we'll test that we can go back and Edit it. + // Get our Submission URL, to parse out the ID of this new submission + cy.location().then(fullUrl => { + // This will be the full path (/workspaceitems/[id]/edit) + const path = fullUrl.pathname; + // Split on the slashes + const subpaths = path.split('/'); + // Part 2 will be the [id] of the submission + const id = subpaths[2]; - // Click the "Save for Later" button to save this submission - cy.get('ds-submission-form-footer [data-test="save-for-later"]').click(); + // Click the "Save for Later" button to save this submission + cy.get('ds-submission-form-footer [data-test="save-for-later"]').click(); - // "Save for Later" should send us to MyDSpace - cy.url().should('include', '/mydspace'); + // "Save for Later" should send us to MyDSpace + cy.url().should('include', '/mydspace'); - // Close any open notifications, to make sure they don't get in the way of next steps - cy.get('[data-dismiss="alert"]').click({multiple: true}); + // Close any open notifications, to make sure they don't get in the way of next steps + cy.get('[data-dismiss="alert"]').click({ multiple: true }); - // This is the GET command that will actually run the search - cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); - // On MyDSpace, find the submission we just created via its ID - cy.get('[data-test="search-box"]').type(id); - cy.get('[data-test="search-button"]').click(); + // This is the GET command that will actually run the search + cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); + // On MyDSpace, find the submission we just created via its ID + cy.get('[data-test="search-box"]').type(id); + cy.get('[data-test="search-button"]').click(); - // Wait for search results to come back from the above GET command - cy.wait('@search-results'); + // Wait for search results to come back from the above GET command + cy.wait('@search-results'); - // Click the Edit button for this in-progress submission - cy.get('#edit_' + id).click(); + // Click the Edit button for this in-progress submission + cy.get('#edit_' + id).click(); - // Should send us back to the submission form - cy.url().should('include', '/workspaceitems/' + id + '/edit'); + // Should send us back to the submission form + cy.url().should('include', '/workspaceitems/' + id + '/edit'); - // Discard our new submission by clicking Discard in Submission form & confirming - cy.get('ds-submission-form-footer [data-test="discard"]').click(); - cy.get('button#discard_submit').click(); + // Discard our new submission by clicking Discard in Submission form & confirming + cy.get('ds-submission-form-footer [data-test="discard"]').click(); + cy.get('button#discard_submit').click(); - // Discarding should send us back to MyDSpace - cy.url().should('include', '/mydspace'); - }); + // Discarding should send us back to MyDSpace + cy.url().should('include', '/mydspace'); }); + }); - it('should let you import from external sources', () => { - cy.visit('/mydspace'); + it('should let you import from external sources', () => { + cy.visit('/mydspace'); - // This page is restricted, so we will be shown the login form. Fill it out & submit. - cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); + // This page is restricted, so we will be shown the login form. Fill it out & submit. + cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); - // Open the New Import dropdown - cy.get('button[data-test="import-dropdown"]').click(); - // Click on the "Item" type in that dropdown - cy.get('#importControlsDropdownMenu button[title="none"]').click(); + // Open the New Import dropdown + cy.get('button[data-test="import-dropdown"]').click(); + // Click on the "Item" type in that dropdown + cy.get('#importControlsDropdownMenu button[title="none"]').click(); - // New URL should include /import-external, as we've moved to the import page - cy.url().should('include', '/import-external'); + // New URL should include /import-external, as we've moved to the import page + cy.url().should('include', '/import-external'); - // The external import searchbox should be visible - cy.get('ds-submission-import-external-searchbar').should('be.visible'); + // The external import searchbox should be visible + cy.get('ds-submission-import-external-searchbar').should('be.visible'); - // Test for accessibility issues - testA11y('ds-submission-import-external'); - }); + // Test for accessibility issues + testA11y('ds-submission-import-external'); + }); }); diff --git a/cypress/e2e/pagenotfound.cy.ts b/cypress/e2e/pagenotfound.cy.ts index d02aa8541c3..968ae2747b5 100644 --- a/cypress/e2e/pagenotfound.cy.ts +++ b/cypress/e2e/pagenotfound.cy.ts @@ -1,18 +1,18 @@ import { testA11y } from 'cypress/support/utils'; describe('PageNotFound', () => { - it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => { - // request an invalid page (UUIDs at root path aren't valid) - cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false }); - cy.get('ds-pagenotfound').should('be.visible'); + it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => { + // request an invalid page (UUIDs at root path aren't valid) + cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false }); + cy.get('ds-pagenotfound').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-pagenotfound'); - }); + // Analyze for accessibility issues + testA11y('ds-pagenotfound'); + }); - it('should not contain element ds-pagenotfound when navigating to existing page', () => { - cy.visit('/home'); - cy.get('ds-pagenotfound').should('not.exist'); - }); + it('should not contain element ds-pagenotfound when navigating to existing page', () => { + cy.visit('/home'); + cy.get('ds-pagenotfound').should('not.exist'); + }); }); diff --git a/cypress/e2e/search-navbar.cy.ts b/cypress/e2e/search-navbar.cy.ts index 28a72bcc786..b1682199161 100644 --- a/cypress/e2e/search-navbar.cy.ts +++ b/cypress/e2e/search-navbar.cy.ts @@ -1,64 +1,64 @@ const page = { - fillOutQueryInNavBar(query) { - // Click the magnifying glass - cy.get('ds-themed-header [data-test="header-search-icon"]').click(); - // Fill out a query in input that appears - cy.get('ds-themed-header [data-test="header-search-box"]').type(query); - }, - submitQueryByPressingEnter() { - cy.get('ds-themed-header [data-test="header-search-box"]').type('{enter}'); - }, - submitQueryByPressingIcon() { - cy.get('ds-themed-header [data-test="header-search-icon"]').click(); - } + fillOutQueryInNavBar(query) { + // Click the magnifying glass + cy.get('ds-themed-header [data-test="header-search-icon"]').click(); + // Fill out a query in input that appears + cy.get('ds-themed-header [data-test="header-search-box"]').type(query); + }, + submitQueryByPressingEnter() { + cy.get('ds-themed-header [data-test="header-search-box"]').type('{enter}'); + }, + submitQueryByPressingIcon() { + cy.get('ds-themed-header [data-test="header-search-icon"]').click(); + }, }; describe('Search from Navigation Bar', () => { - // NOTE: these tests currently assume this query will return results! - const query = Cypress.env('DSPACE_TEST_SEARCH_TERM'); + // NOTE: these tests currently assume this query will return results! + const query = Cypress.env('DSPACE_TEST_SEARCH_TERM'); - it('should go to search page with correct query if submitted (from home)', () => { - cy.visit('/'); - // This is the GET command that will actually run the search - cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); - // Run the search - page.fillOutQueryInNavBar(query); - page.submitQueryByPressingEnter(); - // New URL should include query param - cy.url().should('include', 'query='.concat(query)); - // Wait for search results to come back from the above GET command - cy.wait('@search-results'); - // At least one search result should be displayed - cy.get('[data-test="list-object"]').should('be.visible'); - }); + it('should go to search page with correct query if submitted (from home)', () => { + cy.visit('/'); + // This is the GET command that will actually run the search + cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); + // Run the search + page.fillOutQueryInNavBar(query); + page.submitQueryByPressingEnter(); + // New URL should include query param + cy.url().should('include', 'query='.concat(query)); + // Wait for search results to come back from the above GET command + cy.wait('@search-results'); + // At least one search result should be displayed + cy.get('[data-test="list-object"]').should('be.visible'); + }); - it('should go to search page with correct query if submitted (from search)', () => { - cy.visit('/search'); - // This is the GET command that will actually run the search - cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); - // Run the search - page.fillOutQueryInNavBar(query); - page.submitQueryByPressingEnter(); - // New URL should include query param - cy.url().should('include', 'query='.concat(query)); - // Wait for search results to come back from the above GET command - cy.wait('@search-results'); - // At least one search result should be displayed - cy.get('[data-test="list-object"]').should('be.visible'); - }); + it('should go to search page with correct query if submitted (from search)', () => { + cy.visit('/search'); + // This is the GET command that will actually run the search + cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); + // Run the search + page.fillOutQueryInNavBar(query); + page.submitQueryByPressingEnter(); + // New URL should include query param + cy.url().should('include', 'query='.concat(query)); + // Wait for search results to come back from the above GET command + cy.wait('@search-results'); + // At least one search result should be displayed + cy.get('[data-test="list-object"]').should('be.visible'); + }); - it('should allow user to also submit query by clicking icon', () => { - cy.visit('/'); - // This is the GET command that will actually run the search - cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); - // Run the search - page.fillOutQueryInNavBar(query); - page.submitQueryByPressingIcon(); - // New URL should include query param - cy.url().should('include', 'query='.concat(query)); - // Wait for search results to come back from the above GET command - cy.wait('@search-results'); - // At least one search result should be displayed - cy.get('[data-test="list-object"]').should('be.visible'); - }); + it('should allow user to also submit query by clicking icon', () => { + cy.visit('/'); + // This is the GET command that will actually run the search + cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); + // Run the search + page.fillOutQueryInNavBar(query); + page.submitQueryByPressingIcon(); + // New URL should include query param + cy.url().should('include', 'query='.concat(query)); + // Wait for search results to come back from the above GET command + cy.wait('@search-results'); + // At least one search result should be displayed + cy.get('[data-test="list-object"]').should('be.visible'); + }); }); diff --git a/cypress/e2e/search-page.cy.ts b/cypress/e2e/search-page.cy.ts index 429f4e6da46..62e73c38772 100644 --- a/cypress/e2e/search-page.cy.ts +++ b/cypress/e2e/search-page.cy.ts @@ -1,57 +1,57 @@ -import { Options } from 'cypress-axe'; import { testA11y } from 'cypress/support/utils'; +import { Options } from 'cypress-axe'; describe('Search Page', () => { - // NOTE: these tests currently assume this query will return results! - const query = Cypress.env('DSPACE_TEST_SEARCH_TERM'); + // NOTE: these tests currently assume this query will return results! + const query = Cypress.env('DSPACE_TEST_SEARCH_TERM'); - it('should redirect to the correct url when query was set and submit button was triggered', () => { - const queryString = 'Another interesting query string'; - cy.visit('/search'); - // Type query in searchbox & click search button - cy.get('[data-test="search-box"]').type(queryString); - cy.get('[data-test="search-button"]').click(); - cy.url().should('include', 'query=' + encodeURI(queryString)); - }); + it('should redirect to the correct url when query was set and submit button was triggered', () => { + const queryString = 'Another interesting query string'; + cy.visit('/search'); + // Type query in searchbox & click search button + cy.get('[data-test="search-box"]').type(queryString); + cy.get('[data-test="search-button"]').click(); + cy.url().should('include', 'query=' + encodeURI(queryString)); + }); - it('should load results and pass accessibility tests', () => { - cy.visit('/search?query='.concat(query)); - cy.get('[data-test="search-box"]').should('have.value', query); + it('should load results and pass accessibility tests', () => { + cy.visit('/search?query='.concat(query)); + cy.get('[data-test="search-box"]').should('have.value', query); - // tag must be loaded - cy.get('ds-search-page').should('be.visible'); + // tag must be loaded + cy.get('ds-search-page').should('be.visible'); - // At least one search result should be displayed - cy.get('[data-test="list-object"]').should('be.visible'); + // 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 }); + // 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 for accessibility issues - testA11y('ds-search-page'); - }); + // Analyze for accessibility issues + testA11y('ds-search-page'); + }); - it('should have a working grid view that passes accessibility tests', () => { - cy.visit('/search?query='.concat(query)); + it('should have a working grid view that passes accessibility tests', () => { + cy.visit('/search?query='.concat(query)); - // Click button in sidebar to display grid view - cy.get('ds-search-sidebar [data-test="grid-view"]').click(); + // Click button in sidebar to display grid view + cy.get('ds-search-sidebar [data-test="grid-view"]').click(); - // tag must be loaded - cy.get('ds-search-page').should('be.visible'); + // tag must be loaded + cy.get('ds-search-page').should('be.visible'); - // At least one grid object (card) should be displayed - cy.get('[data-test="grid-object"]').should('be.visible'); + // At least one grid object (card) should be displayed + cy.get('[data-test="grid-object"]').should('be.visible'); - // Analyze for accessibility issues - testA11y('ds-search-page', + // Analyze for accessibility issues + testA11y('ds-search-page', { - rules: { - // Card titles fail this test currently - 'heading-order': { enabled: false } - } - } as Options - ); - }); + rules: { + // Card titles fail this test currently + 'heading-order': { enabled: false }, + }, + } as Options, + ); + }); }); diff --git a/cypress/e2e/submission.cy.ts b/cypress/e2e/submission.cy.ts index 4402410f234..7123d841345 100644 --- a/cypress/e2e/submission.cy.ts +++ b/cypress/e2e/submission.cy.ts @@ -4,224 +4,224 @@ import { Options } from 'cypress-axe'; describe('New Submission page', () => { - // NOTE: We already test that new Item submissions can be started from MyDSpace in my-dspace.spec.ts - it('should create a new submission when using /submit path & pass accessibility', () => { - // Test that calling /submit with collection & entityType will create a new submission - cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none')); + // NOTE: We already test that new Item submissions can be started from MyDSpace in my-dspace.spec.ts + it('should create a new submission when using /submit path & pass accessibility', () => { + // Test that calling /submit with collection & entityType will create a new submission + cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none')); - // This page is restricted, so we will be shown the login form. Fill it out & submit. - cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); + // This page is restricted, so we will be shown the login form. Fill it out & submit. + cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); - // Should redirect to /workspaceitems, as we've started a new submission - cy.url().should('include', '/workspaceitems'); + // Should redirect to /workspaceitems, as we've started a new submission + cy.url().should('include', '/workspaceitems'); - // The Submission edit form tag should be visible - cy.get('ds-submission-edit').should('be.visible'); + // The Submission edit form tag should be visible + cy.get('ds-submission-edit').should('be.visible'); - // A Collection menu button should exist & it's value should be the selected collection - cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')); + // A Collection menu button should exist & it's value should be the selected collection + cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')); - // 4 sections should be visible by default - cy.get('div#section_traditionalpageone').should('be.visible'); - cy.get('div#section_traditionalpagetwo').should('be.visible'); - cy.get('div#section_upload').should('be.visible'); - cy.get('div#section_license').should('be.visible'); + // 4 sections should be visible by default + cy.get('div#section_traditionalpageone').should('be.visible'); + cy.get('div#section_traditionalpagetwo').should('be.visible'); + cy.get('div#section_upload').should('be.visible'); + cy.get('div#section_license').should('be.visible'); - // Test entire page for accessibility - testA11y('ds-submission-edit', + // Test entire page for accessibility + testA11y('ds-submission-edit', { - rules: { - // Author & Subject fields have invalid "aria-multiline" attrs. - // See https://github.com/DSpace/dspace-angular/issues/1272 - 'aria-allowed-attr': { enabled: false }, - // 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 }, - // All select boxes fail to have a name / aria-label. - // This is a bug in ng-dynamic-forms and may require https://github.com/DSpace/dspace-angular/issues/2216 - 'select-name': { enabled: false }, - } - - } as Options - ); - - // Discard button should work - // Clicking it will display a confirmation, which we will confirm with another click - cy.get('button#discard').click(); - cy.get('button#discard_submit').click(); + rules: { + // Author & Subject fields have invalid "aria-multiline" attrs. + // See https://github.com/DSpace/dspace-angular/issues/1272 + 'aria-allowed-attr': { enabled: false }, + // 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 }, + // All select boxes fail to have a name / aria-label. + // This is a bug in ng-dynamic-forms and may require https://github.com/DSpace/dspace-angular/issues/2216 + 'select-name': { enabled: false }, + }, + + } as Options, + ); + + // Discard button should work + // Clicking it will display a confirmation, which we will confirm with another click + cy.get('button#discard').click(); + cy.get('button#discard_submit').click(); + }); + + it('should block submission & show errors if required fields are missing', () => { + // Create a new submission + cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none')); + + // This page is restricted, so we will be shown the login form. Fill it out & submit. + cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); + + // Attempt an immediate deposit without filling out any fields + cy.get('button#deposit').click(); + + // A warning alert should display. + cy.get('ds-notification div.alert-success').should('not.exist'); + cy.get('ds-notification div.alert-warning').should('be.visible'); + + // First section should have an exclamation error in the header + // (as it has required fields) + cy.get('div#traditionalpageone-header i.fa-exclamation-circle').should('be.visible'); + + // Title field should have class "is-invalid" applied, as it's required + cy.get('input#dc_title').should('have.class', 'is-invalid'); + + // Date Year field should also have "is-valid" class + cy.get('input#dc_date_issued_year').should('have.class', 'is-invalid'); + + // FINALLY, cleanup after ourselves. This also exercises the MyDSpace delete button. + // Get our Submission URL, to parse out the ID of this submission + cy.location().then(fullUrl => { + // This will be the full path (/workspaceitems/[id]/edit) + const path = fullUrl.pathname; + // Split on the slashes + const subpaths = path.split('/'); + // Part 2 will be the [id] of the submission + const id = subpaths[2]; + + // Even though form is incomplete, the "Save for Later" button should still work + cy.get('button#saveForLater').click(); + + // "Save for Later" should send us to MyDSpace + cy.url().should('include', '/mydspace'); + + // A success alert should be visible + cy.get('ds-notification div.alert-success').should('be.visible'); + // Now, dismiss any open alert boxes (may be multiple, as tests run quickly) + cy.get('[data-dismiss="alert"]').click({ multiple: true }); + + // This is the GET command that will actually run the search + cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); + // On MyDSpace, find the submission we just saved via its ID + cy.get('[data-test="search-box"]').type(id); + cy.get('[data-test="search-button"]').click(); + + // Wait for search results to come back from the above GET command + cy.wait('@search-results'); + + // Delete our created submission & confirm deletion + cy.get('button#delete_' + id).click(); + cy.get('button#delete_confirm').click(); }); + }); - it('should block submission & show errors if required fields are missing', () => { - // Create a new submission - cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none')); + it('should allow for deposit if all required fields completed & file uploaded', () => { + // Create a new submission + cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none')); - // This page is restricted, so we will be shown the login form. Fill it out & submit. - cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); + // This page is restricted, so we will be shown the login form. Fill it out & submit. + cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); - // Attempt an immediate deposit without filling out any fields - cy.get('button#deposit').click(); + // Fill out all required fields (Title, Date) + cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests'); + cy.get('input#dc_date_issued_year').type('2022'); - // A warning alert should display. - cy.get('ds-notification div.alert-success').should('not.exist'); - cy.get('ds-notification div.alert-warning').should('be.visible'); + // Confirm the required license by checking checkbox + // (NOTE: requires "force:true" cause Cypress claims this checkbox is covered by its own ) + cy.get('input#granted').check( { force: true } ); - // First section should have an exclamation error in the header - // (as it has required fields) - cy.get('div#traditionalpageone-header i.fa-exclamation-circle').should('be.visible'); + // Before using Cypress drag & drop, we have to manually trigger the "dragover" event. + // This ensures our UI displays the dropzone that covers the entire submission page. + // (For some reason Cypress drag & drop doesn't trigger this even itself & upload won't work without this trigger) + cy.get('ds-uploader').trigger('dragover'); - // Title field should have class "is-invalid" applied, as it's required - cy.get('input#dc_title').should('have.class', 'is-invalid'); + // This is the POST command that will upload the file + cy.intercept('POST', '/server/api/submission/workspaceitems/*').as('upload'); - // Date Year field should also have "is-valid" class - cy.get('input#dc_date_issued_year').should('have.class', 'is-invalid'); - - // FINALLY, cleanup after ourselves. This also exercises the MyDSpace delete button. - // Get our Submission URL, to parse out the ID of this submission - cy.location().then(fullUrl => { - // This will be the full path (/workspaceitems/[id]/edit) - const path = fullUrl.pathname; - // Split on the slashes - const subpaths = path.split('/'); - // Part 2 will be the [id] of the submission - const id = subpaths[2]; - - // Even though form is incomplete, the "Save for Later" button should still work - cy.get('button#saveForLater').click(); - - // "Save for Later" should send us to MyDSpace - cy.url().should('include', '/mydspace'); - - // A success alert should be visible - cy.get('ds-notification div.alert-success').should('be.visible'); - // Now, dismiss any open alert boxes (may be multiple, as tests run quickly) - cy.get('[data-dismiss="alert"]').click({multiple: true}); - - // This is the GET command that will actually run the search - cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); - // On MyDSpace, find the submission we just saved via its ID - cy.get('[data-test="search-box"]').type(id); - cy.get('[data-test="search-button"]').click(); - - // Wait for search results to come back from the above GET command - cy.wait('@search-results'); - - // Delete our created submission & confirm deletion - cy.get('button#delete_' + id).click(); - cy.get('button#delete_confirm').click(); - }); + // Upload our DSpace logo via drag & drop onto submission form + // cy.get('div#section_upload') + cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.png', { + action: 'drag-drop', }); - it('should allow for deposit if all required fields completed & file uploaded', () => { - // Create a new submission - cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none')); + // Wait for upload to complete before proceeding + cy.wait('@upload'); - // This page is restricted, so we will be shown the login form. Fill it out & submit. - cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD')); + // Wait for deposit button to not be disabled & click it. + cy.get('button#deposit').should('not.be.disabled').click(); - // Fill out all required fields (Title, Date) - cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests'); - cy.get('input#dc_date_issued_year').type('2022'); + // No warnings should exist. Instead, just successful deposit alert is displayed + cy.get('ds-notification div.alert-warning').should('not.exist'); + cy.get('ds-notification div.alert-success').should('be.visible'); + }); - // Confirm the required license by checking checkbox - // (NOTE: requires "force:true" cause Cypress claims this checkbox is covered by its own ) - cy.get('input#granted').check( {force: true} ); + it('is possible to submit a new "Person" and that form passes accessibility', () => { + // To submit a different entity type, we'll start from MyDSpace + cy.visit('/mydspace'); - // Before using Cypress drag & drop, we have to manually trigger the "dragover" event. - // This ensures our UI displays the dropzone that covers the entire submission page. - // (For some reason Cypress drag & drop doesn't trigger this even itself & upload won't work without this trigger) - cy.get('ds-uploader').trigger('dragover'); + // This page is restricted, so we will be shown the login form. Fill it out & submit. + // NOTE: At this time, we MUST login as admin to submit Person objects + cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); - // This is the POST command that will upload the file - cy.intercept('POST', '/server/api/submission/workspaceitems/*').as('upload'); + // Open the New Submission dropdown + cy.get('button[data-test="submission-dropdown"]').click(); + // Click on the "Person" type in that dropdown + cy.get('#entityControlsDropdownMenu button[title="Person"]').click(); - // Upload our DSpace logo via drag & drop onto submission form - // cy.get('div#section_upload') - cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.png', { - action: 'drag-drop' - }); + // This should display the (popup window) + cy.get('ds-create-item-parent-selector').should('be.visible'); - // Wait for upload to complete before proceeding - cy.wait('@upload'); + // Type in a known Collection name in the search box + cy.get('ds-authorized-collection-selector input[type="search"]').type(Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME')); - // Wait for deposit button to not be disabled & click it. - cy.get('button#deposit').should('not.be.disabled').click(); - - // No warnings should exist. Instead, just successful deposit alert is displayed - cy.get('ds-notification div.alert-warning').should('not.exist'); - cy.get('ds-notification div.alert-success').should('be.visible'); - }); + // Click on the button matching that known Collection name + cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME')).concat('"]')).click(); - it('is possible to submit a new "Person" and that form passes accessibility', () => { - // To submit a different entity type, we'll start from MyDSpace - cy.visit('/mydspace'); + // New URL should include /workspaceitems, as we've started a new submission + cy.url().should('include', '/workspaceitems'); - // This page is restricted, so we will be shown the login form. Fill it out & submit. - // NOTE: At this time, we MUST login as admin to submit Person objects - cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); + // The Submission edit form tag should be visible + cy.get('ds-submission-edit').should('be.visible'); - // Open the New Submission dropdown - cy.get('button[data-test="submission-dropdown"]').click(); - // Click on the "Person" type in that dropdown - cy.get('#entityControlsDropdownMenu button[title="Person"]').click(); + // A Collection menu button should exist & its value should be the selected collection + cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME')); - // This should display the (popup window) - cy.get('ds-create-item-parent-selector').should('be.visible'); + // 3 sections should be visible by default + cy.get('div#section_personStep').should('be.visible'); + cy.get('div#section_upload').should('be.visible'); + cy.get('div#section_license').should('be.visible'); - // Type in a known Collection name in the search box - cy.get('ds-authorized-collection-selector input[type="search"]').type(Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME')); - - // Click on the button matching that known Collection name - cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME')).concat('"]')).click(); - - // New URL should include /workspaceitems, as we've started a new submission - cy.url().should('include', '/workspaceitems'); - - // The Submission edit form tag should be visible - cy.get('ds-submission-edit').should('be.visible'); - - // A Collection menu button should exist & its value should be the selected collection - cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME')); - - // 3 sections should be visible by default - cy.get('div#section_personStep').should('be.visible'); - cy.get('div#section_upload').should('be.visible'); - cy.get('div#section_license').should('be.visible'); - - // Test entire page for accessibility - testA11y('ds-submission-edit', + // Test entire page for accessibility + testA11y('ds-submission-edit', { - 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 - ); - - // Click the lookup button next to "Publication" field - cy.get('button[data-test="lookup-button"]').click(); - - // A popup modal window should be visible - cy.get('ds-dynamic-lookup-relation-modal').should('be.visible'); - - // Popup modal should also pass accessibility tests - //testA11y('ds-dynamic-lookup-relation-modal'); - testA11y({ - include: ['ds-dynamic-lookup-relation-modal'], - exclude: [ - ['ul.nav-tabs'] // Tabs at top of model have several issues which seem to be caused by ng-bootstrap - ], - }); - - // Close popup window - cy.get('ds-dynamic-lookup-relation-modal button.close').click(); - - // Back on the form, click the discard button to remove new submission - // Clicking it will display a confirmation, which we will confirm with another click - cy.get('button#discard').click(); - cy.get('button#discard_submit').click(); + 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, + ); + + // Click the lookup button next to "Publication" field + cy.get('button[data-test="lookup-button"]').click(); + + // A popup modal window should be visible + cy.get('ds-dynamic-lookup-relation-modal').should('be.visible'); + + // Popup modal should also pass accessibility tests + //testA11y('ds-dynamic-lookup-relation-modal'); + testA11y({ + include: ['ds-dynamic-lookup-relation-modal'], + exclude: [ + ['ul.nav-tabs'], // Tabs at top of model have several issues which seem to be caused by ng-bootstrap + ], }); + + // Close popup window + cy.get('ds-dynamic-lookup-relation-modal button.close').click(); + + // Back on the form, click the discard button to remove new submission + // Clicking it will display a confirmation, which we will confirm with another click + cy.get('button#discard').click(); + cy.get('button#discard_submit').click(); + }); }); diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts index cc3dccba38e..091f11d0f7e 100644 --- a/cypress/plugins/index.ts +++ b/cypress/plugins/index.ts @@ -9,51 +9,51 @@ let REST_DOMAIN: string; // Plugins enable you to tap into, modify, or extend the internal behavior of Cypress // For more info, visit https://on.cypress.io/plugins-api module.exports = (on, config) => { - on('task', { - // 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 - log(message: string) { - console.log(message); - return null; - }, - table(message: string) { - console.table(message); - return null; - }, - // Cypress doesn't have access to the running application in Node.js. - // So, it's not possible to inject or load the AppConfig or environment of the Angular UI. - // Instead, we'll read our running application's config.json, which contains the configs & - // is regenerated at runtime each time the Angular UI application starts up. - readUIConfig() { - // Check if we have a config.json in the src/assets. If so, use that. - // This is where it's written when running "ng e2e" or "yarn serve" - if (fs.existsSync('./src/assets/config.json')) { - return fs.readFileSync('./src/assets/config.json', 'utf8'); - // Otherwise, check the dist/browser/assets - // This is where it's written when running "serve:ssr", which is what CI uses to start the frontend - } else if (fs.existsSync('./dist/browser/assets/config.json')) { - return fs.readFileSync('./dist/browser/assets/config.json', 'utf8'); - } + on('task', { + // 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 + log(message: string) { + console.log(message); + return null; + }, + table(message: string) { + console.table(message); + return null; + }, + // Cypress doesn't have access to the running application in Node.js. + // So, it's not possible to inject or load the AppConfig or environment of the Angular UI. + // Instead, we'll read our running application's config.json, which contains the configs & + // is regenerated at runtime each time the Angular UI application starts up. + readUIConfig() { + // Check if we have a config.json in the src/assets. If so, use that. + // This is where it's written when running "ng e2e" or "yarn serve" + if (fs.existsSync('./src/assets/config.json')) { + return fs.readFileSync('./src/assets/config.json', 'utf8'); + // Otherwise, check the dist/browser/assets + // This is where it's written when running "serve:ssr", which is what CI uses to start the frontend + } else if (fs.existsSync('./dist/browser/assets/config.json')) { + return fs.readFileSync('./dist/browser/assets/config.json', 'utf8'); + } - return null; - }, - // Save value of REST Base URL, looked up before all tests. - // This allows other tests to use it easily via getRestBaseURL() below. - saveRestBaseURL(url: string) { - return (REST_BASE_URL = url); - }, - // Retrieve currently saved value of REST Base URL - getRestBaseURL() { - return REST_BASE_URL ; - }, - // Save value of REST Domain, looked up before all tests. - // This allows other tests to use it easily via getRestBaseDomain() below. - saveRestBaseDomain(domain: string) { - return (REST_DOMAIN = domain); - }, - // Retrieve currently saved value of REST Domain - getRestBaseDomain() { - return REST_DOMAIN ; - } - }); + return null; + }, + // Save value of REST Base URL, looked up before all tests. + // This allows other tests to use it easily via getRestBaseURL() below. + saveRestBaseURL(url: string) { + return (REST_BASE_URL = url); + }, + // Retrieve currently saved value of REST Base URL + getRestBaseURL() { + return REST_BASE_URL ; + }, + // Save value of REST Domain, looked up before all tests. + // This allows other tests to use it easily via getRestBaseDomain() below. + saveRestBaseDomain(domain: string) { + return (REST_DOMAIN = domain); + }, + // Retrieve currently saved value of REST Domain + getRestBaseDomain() { + return REST_DOMAIN ; + }, + }); }; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 7da454e2d0c..b3e3b9630bb 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -3,8 +3,14 @@ // See docs at https://docs.cypress.io/api/cypress-api/custom-commands // *********************************************** -import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; -import { DSPACE_XSRF_COOKIE, XSRF_REQUEST_HEADER } from 'src/app/core/xsrf/xsrf.constants'; +import { + AuthTokenInfo, + TOKENITEM, +} from 'src/app/core/auth/models/auth-token-info.model'; +import { + DSPACE_XSRF_COOKIE, + XSRF_REQUEST_HEADER, +} from 'src/app/core/xsrf/xsrf.constants'; import { v4 as uuidv4 } from 'uuid'; // Declare Cypress namespace to help with Intellisense & code completion in IDEs @@ -57,33 +63,33 @@ declare global { * @param password password to login as */ function login(email: string, password: string): void { - // Create a fake CSRF cookie/token to use in POST - cy.createCSRFCookie().then((csrfToken: string) => { - // get our REST API's base URL, also needed for POST - cy.task('getRestBaseURL').then((baseRestUrl: string) => { - // Now, send login POST request including that CSRF token - cy.request({ - method: 'POST', - url: baseRestUrl + '/api/authn/login', - headers: { [XSRF_REQUEST_HEADER]: csrfToken}, - form: true, // indicates the body should be form urlencoded - body: { user: email, password: password } - }).then((resp) => { - // We expect a successful login - expect(resp.status).to.eq(200); - // We expect to have a valid authorization header returned (with our auth token) - expect(resp.headers).to.have.property('authorization'); + // Create a fake CSRF cookie/token to use in POST + cy.createCSRFCookie().then((csrfToken: string) => { + // get our REST API's base URL, also needed for POST + cy.task('getRestBaseURL').then((baseRestUrl: string) => { + // Now, send login POST request including that CSRF token + cy.request({ + method: 'POST', + url: baseRestUrl + '/api/authn/login', + headers: { [XSRF_REQUEST_HEADER]: csrfToken }, + form: true, // indicates the body should be form urlencoded + body: { user: email, password: password }, + }).then((resp) => { + // We expect a successful login + expect(resp.status).to.eq(200); + // We expect to have a valid authorization header returned (with our auth token) + expect(resp.headers).to.have.property('authorization'); - // Initialize our AuthTokenInfo object from the authorization header. - const authheader = resp.headers.authorization as string; - const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader); + // Initialize our AuthTokenInfo object from the authorization header. + const authheader = resp.headers.authorization as string; + const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader); - // Save our AuthTokenInfo object to our dsAuthInfo UI cookie - // This ensures the UI will recognize we are logged in on next "visit()" - cy.setCookie(TOKENITEM, JSON.stringify(authinfo)); - }); - }); + // Save our AuthTokenInfo object to our dsAuthInfo UI cookie + // This ensures the UI will recognize we are logged in on next "visit()" + cy.setCookie(TOKENITEM, JSON.stringify(authinfo)); + }); }); + }); } // Add as a Cypress command (i.e. assign to 'cy.login') Cypress.Commands.add('login', login); @@ -94,12 +100,12 @@ Cypress.Commands.add('login', login); * @param password password to login as */ function loginViaForm(email: string, password: string): void { - // Enter email - cy.get('ds-log-in [data-test="email"]').type(email); - // Enter password - cy.get('ds-log-in [data-test="password"]').type(password); - // Click login button - cy.get('ds-log-in [data-test="login-button"]').click(); + // Enter email + cy.get('ds-log-in [data-test="email"]').type(email); + // Enter password + cy.get('ds-log-in [data-test="password"]').type(password); + // Click login button + cy.get('ds-log-in [data-test="login-button"]').click(); } // Add as a Cypress command (i.e. assign to 'cy.loginViaForm') Cypress.Commands.add('loginViaForm', loginViaForm); @@ -117,29 +123,29 @@ Cypress.Commands.add('loginViaForm', loginViaForm); * @param dsoType type of DSpace Object (e.g. "item", "collection", "community") */ function generateViewEvent(uuid: string, dsoType: string): void { - // Create a fake CSRF cookie/token to use in POST - cy.createCSRFCookie().then((csrfToken: string) => { - // get our REST API's base URL, also needed for POST - cy.task('getRestBaseURL').then((baseRestUrl: string) => { - // Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header - cy.request({ - method: 'POST', - url: baseRestUrl + '/api/statistics/viewevents', - headers: { - [XSRF_REQUEST_HEADER] : csrfToken, - // use a known public IP address to avoid being seen as a "bot" - 'X-Forwarded-For': '1.1.1.1', - // Use a user-agent of a Firefox browser on Windows. This again avoids being seen as a "bot" - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0', - }, - //form: true, // indicates the body should be form urlencoded - body: { targetId: uuid, targetType: dsoType }, - }).then((resp) => { - // We expect a 201 (which means statistics event was created) - expect(resp.status).to.eq(201); - }); - }); + // Create a fake CSRF cookie/token to use in POST + cy.createCSRFCookie().then((csrfToken: string) => { + // get our REST API's base URL, also needed for POST + cy.task('getRestBaseURL').then((baseRestUrl: string) => { + // Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header + cy.request({ + method: 'POST', + url: baseRestUrl + '/api/statistics/viewevents', + headers: { + [XSRF_REQUEST_HEADER] : csrfToken, + // use a known public IP address to avoid being seen as a "bot" + 'X-Forwarded-For': '1.1.1.1', + // Use a user-agent of a Firefox browser on Windows. This again avoids being seen as a "bot" + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0', + }, + //form: true, // indicates the body should be form urlencoded + body: { targetId: uuid, targetType: dsoType }, + }).then((resp) => { + // We expect a 201 (which means statistics event was created) + expect(resp.status).to.eq(201); + }); }); + }); } // Add as a Cypress command (i.e. assign to 'cy.generateViewEvent') Cypress.Commands.add('generateViewEvent', generateViewEvent); @@ -153,17 +159,17 @@ Cypress.Commands.add('generateViewEvent', generateViewEvent); * @returns a Cypress Chainable which can be used to get the generated CSRF Token */ function createCSRFCookie(): Cypress.Chainable { - // Generate a new token which is a random UUID - const csrfToken: string = uuidv4(); + // Generate a new token which is a random UUID + const csrfToken: string = uuidv4(); - // Save it to our required cookie - cy.task('getRestBaseDomain').then((baseDomain: string) => { - // Create a fake CSRF Token. Set it in the required server-side cookie - cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain }); - }); + // Save it to our required cookie + cy.task('getRestBaseDomain').then((baseDomain: string) => { + // Create a fake CSRF Token. Set it in the required server-side cookie + cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain }); + }); - // return the generated token wrapped in a chainable - return cy.wrap(csrfToken); + // return the generated token wrapped in a chainable + return cy.wrap(csrfToken); } // Add as a Cypress command (i.e. assign to 'cy.createCSRFCookie') Cypress.Commands.add('createCSRFCookie', createCSRFCookie); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index b2255a7da65..73d3c76a990 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -15,10 +15,10 @@ // Import all custom Commands (from commands.ts) for all tests import './commands'; - // Import Cypress Axe tools for all tests // https://github.com/component-driven/cypress-axe import 'cypress-axe'; + import { DSPACE_XSRF_COOKIE } from 'src/app/core/xsrf/xsrf.constants'; // Runs once before all tests @@ -34,18 +34,18 @@ before(() => { // Find URL of our REST API & save to global variable via task let baseRestUrl = FALLBACK_TEST_REST_BASE_URL; if (!config.rest.baseUrl) { - console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL); + console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL); } else { - baseRestUrl = config.rest.baseUrl; + baseRestUrl = config.rest.baseUrl; } cy.task('saveRestBaseURL', baseRestUrl); // Find domain of our REST API & save to global variable via task. let baseDomain = FALLBACK_TEST_REST_DOMAIN; if (!config.rest.host) { - console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN); + console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN); } else { - baseDomain = config.rest.host; + baseDomain = config.rest.host; } cy.task('saveRestBaseDomain', baseDomain); @@ -54,12 +54,12 @@ before(() => { // Runs once before the first test in each "block" beforeEach(() => { - // Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie - // This just ensures it doesn't get in the way of matching other objects in the page. - cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}'); + // Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie + // This just ensures it doesn't get in the way of matching other objects in the page. + cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}'); - // Remove any CSRF cookies saved from prior tests - cy.clearCookie(DSPACE_XSRF_COOKIE); + // Remove any CSRF cookies saved from prior tests + cy.clearCookie(DSPACE_XSRF_COOKIE); }); // NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL diff --git a/cypress/support/utils.ts b/cypress/support/utils.ts index 96575969e85..9a9ea1121ba 100644 --- a/cypress/support/utils.ts +++ b/cypress/support/utils.ts @@ -5,26 +5,26 @@ import { Options } from 'cypress-axe'; // 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) - }) - ); + 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); + // 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); } @@ -32,13 +32,13 @@ function terminalLog(violations: Result[]) { // 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); + 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); }; diff --git a/package.json b/package.json index c0a3843605e..77227ff2d21 100644 --- a/package.json +++ b/package.json @@ -55,28 +55,28 @@ "ts-node": "10.2.1" }, "dependencies": { - "@angular/animations": "^15.2.8", - "@angular/cdk": "^15.2.8", - "@angular/common": "^15.2.8", - "@angular/compiler": "^15.2.8", - "@angular/core": "^15.2.8", - "@angular/forms": "^15.2.8", - "@angular/localize": "15.2.8", - "@angular/platform-browser": "^15.2.8", - "@angular/platform-browser-dynamic": "^15.2.8", - "@angular/platform-server": "^15.2.8", - "@angular/router": "^15.2.8", + "@angular/animations": "^16.2.12", + "@angular/cdk": "^16.2.12", + "@angular/common": "^16.2.12", + "@angular/compiler": "^16.2.12", + "@angular/core": "^16.2.12", + "@angular/forms": "^16.2.12", + "@angular/localize": "16.2.12", + "@angular/platform-browser": "^16.2.12", + "@angular/platform-browser-dynamic": "^16.2.12", + "@angular/platform-server": "^16.2.12", + "@angular/router": "^16.2.12", "@babel/runtime": "7.21.0", "@kolkov/ngx-gallery": "^2.0.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.11.3", "@ng-bootstrap/ng-bootstrap": "^11.0.0", - "@ng-dynamic-forms/core": "^15.0.0", - "@ng-dynamic-forms/ui-ng-bootstrap": "^15.0.0", - "@ngrx/effects": "^15.4.0", - "@ngrx/router-store": "^15.4.0", - "@ngrx/store": "^15.4.0", - "@nguniversal/express-engine": "^15.2.1", + "@ng-dynamic-forms/core": "^16.0.0", + "@ng-dynamic-forms/ui-ng-bootstrap": "^16.0.0", + "@ngrx/effects": "^16.3.0", + "@ngrx/router-store": "^16.3.0", + "@ngrx/store": "^16.3.0", + "@nguniversal/express-engine": "^16.2.0", "@ngx-translate/core": "^14.0.0", "@nicky-lenaers/ngx-scroll-to": "^14.0.0", "@types/grecaptcha": "^3.0.4", @@ -94,7 +94,7 @@ "date-fns-tz": "^1.3.7", "deepmerge": "^4.3.1", "ejs": "^3.1.9", - "express": "^4.18.2", + "express": "^4.19.2", "express-rate-limit": "^5.1.3", "fast-json-patch": "^3.1.1", "filesize": "^6.1.0", @@ -110,17 +110,15 @@ "lodash": "^4.17.21", "lru-cache": "^7.14.1", "markdown-it": "^13.0.1", - "markdown-it-mathjax3": "^4.3.2", "mirador": "^3.3.0", "mirador-dl-plugin": "^0.13.0", "mirador-share-plugin": "^0.11.0", "morgan": "^1.10.0", "ng-mocks": "^14.10.0", - "ng2-file-upload": "1.4.0", + "ng2-file-upload": "5.0.0", "ng2-nouislider": "^2.0.0", - "ngx-infinite-scroll": "^15.0.0", + "ngx-infinite-scroll": "^16.0.0", "ngx-pagination": "6.0.3", - "ngx-sortablejs": "^11.1.0", "ngx-ui-switch": "^14.1.0", "nouislider": "^15.7.1", "pem": "1.14.7", @@ -132,24 +130,24 @@ "sortablejs": "1.15.0", "uuid": "^8.3.2", "webfontloader": "1.6.28", - "zone.js": "~0.11.5" + "zone.js": "~0.13.3" }, "devDependencies": { - "@angular-builders/custom-webpack": "~15.0.0", - "@angular-devkit/build-angular": "^15.2.6", - "@angular-eslint/builder": "15.2.1", - "@angular-eslint/eslint-plugin": "15.2.1", - "@angular-eslint/eslint-plugin-template": "15.2.1", - "@angular-eslint/schematics": "15.2.1", - "@angular-eslint/template-parser": "15.2.1", - "@angular/cli": "^16.0.4", - "@angular/compiler-cli": "^15.2.8", - "@angular/language-service": "^15.2.8", + "@angular-builders/custom-webpack": "~16.0.0", + "@angular-devkit/build-angular": "^16.2.12", + "@angular-eslint/builder": "16.3.1", + "@angular-eslint/eslint-plugin": "16.3.1", + "@angular-eslint/eslint-plugin-template": "16.3.1", + "@angular-eslint/schematics": "16.3.1", + "@angular-eslint/template-parser": "16.3.1", + "@angular/cli": "^16.2.12", + "@angular/compiler-cli": "^16.2.12", + "@angular/language-service": "^16.2.12", "@cypress/schematic": "^1.5.0", "@fortawesome/fontawesome-free": "^6.4.0", - "@ngrx/store-devtools": "^15.4.0", - "@ngtools/webpack": "^15.2.6", - "@nguniversal/builders": "^15.2.1", + "@ngrx/store-devtools": "^16.3.0", + "@ngtools/webpack": "^16.2.12", + "@nguniversal/builders": "^16.2.0", "@types/deep-freeze": "0.1.2", "@types/ejs": "^3.1.2", "@types/express": "^4.17.17", @@ -186,7 +184,7 @@ "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "karma-mocha-reporter": "2.2.5", - "ngx-mask": "^13.1.7", + "ngx-mask": "14.2.4", "nodemon": "^2.0.22", "postcss": "^8.4", "postcss-apply": "0.12.0", @@ -202,7 +200,7 @@ "sass-loader": "^12.6.0", "sass-resources-loader": "^2.2.5", "ts-node": "^8.10.2", - "typescript": "~4.8.4", + "typescript": "~4.9.3", "webpack": "5.76.1", "webpack-bundle-analyzer": "^4.8.0", "webpack-cli": "^4.2.0", diff --git a/server.ts b/server.ts index da085f372fd..d00529687de 100644 --- a/server.ts +++ b/server.ts @@ -48,7 +48,7 @@ import { hasNoValue, hasValue } from './src/app/shared/empty.util'; import { UIServerConfig } from './src/config/ui-server-config.interface'; -import { ServerAppModule } from './src/main.server'; +import bootstrap from './src/main.server'; import { buildAppConfig } from './src/config/config.server'; import { APP_CONFIG, AppConfig } from './src/config/app-config.interface'; @@ -130,7 +130,8 @@ export function app() { // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) server.engine('html', (_, options, callback) => ngExpressEngine({ - bootstrap: ServerAppModule, + bootstrap, + inlineCriticalCss: environment.universal.inlineCriticalCss, providers: [ { provide: REQUEST, @@ -142,10 +143,10 @@ export function app() { }, { provide: APP_CONFIG, - useValue: environment - } - ] - })(_, (options as any), callback) + useValue: environment, + }, + ], + })(_, (options as any), callback), ); server.engine('ejs', ejs.renderFile); @@ -162,7 +163,7 @@ export function app() { server.get('/robots.txt', (req, res) => { res.setHeader('content-type', 'text/plain'); res.render('assets/robots.txt.ejs', { - 'origin': req.protocol + '://' + req.headers.host + 'origin': req.protocol + '://' + req.headers.host, }); }); @@ -177,7 +178,7 @@ export function app() { router.use('/sitemap**', createProxyMiddleware({ target: `${environment.rest.baseUrl}/sitemaps`, pathRewrite: path => path.replace(environment.ui.nameSpace, '/'), - changeOrigin: true + changeOrigin: true, })); /** @@ -186,7 +187,7 @@ export function app() { router.use('/signposting**', createProxyMiddleware({ target: `${environment.rest.baseUrl}`, pathRewrite: path => path.replace(environment.ui.nameSpace, '/'), - changeOrigin: true + changeOrigin: true, })); /** @@ -197,7 +198,7 @@ export function app() { const RateLimit = require('express-rate-limit'); const limiter = new RateLimit({ windowMs: (environment.ui as UIServerConfig).rateLimiter.windowMs, - max: (environment.ui as UIServerConfig).rateLimiter.max + max: (environment.ui as UIServerConfig).rateLimiter.max, }); server.use(limiter); } @@ -325,7 +326,7 @@ function initCache() { botCache = new LRU( { max: environment.cache.serverSide.botCache.max, ttl: environment.cache.serverSide.botCache.timeToLive, - allowStale: environment.cache.serverSide.botCache.allowStale + allowStale: environment.cache.serverSide.botCache.allowStale, }); } @@ -337,7 +338,7 @@ function initCache() { anonymousCache = new LRU( { max: environment.cache.serverSide.anonymousCache.max, ttl: environment.cache.serverSide.anonymousCache.timeToLive, - allowStale: environment.cache.serverSide.anonymousCache.allowStale + allowStale: environment.cache.serverSide.anonymousCache.allowStale, }); } } @@ -415,7 +416,7 @@ function checkCacheForRequest(cacheName: string, cache: LRU, req, r const key = getCacheKey(req); // Check if this page is in our cache - let cachedCopy = cache.get(key); + const cachedCopy = cache.get(key); if (cachedCopy) { if (environment.cache.serverSide.debug) { console.log(`CACHE HIT FOR ${key} in ${cacheName} cache`); } @@ -529,20 +530,20 @@ function serverStarted() { function createHttpsServer(keys) { const listener = createServer({ key: keys.serviceKey, - cert: keys.certificate + cert: keys.certificate, }, app).listen(environment.ui.port, environment.ui.host, () => { serverStarted(); }); // Graceful shutdown when signalled - const terminator = createHttpTerminator({server: listener}); + const terminator = createHttpTerminator({ server: listener }); process.on('SIGINT', () => { - void (async ()=> { - console.debug('Closing HTTPS server on signal'); - await terminator.terminate().catch(e => { console.error(e); }); - console.debug('HTTPS server closed'); - })(); - }); + void (async ()=> { + console.debug('Closing HTTPS server on signal'); + await terminator.terminate().catch(e => { console.error(e); }); + console.debug('HTTPS server closed'); + })(); + }); } /** @@ -559,14 +560,14 @@ function run() { }); // Graceful shutdown when signalled - const terminator = createHttpTerminator({server: listener}); + const terminator = createHttpTerminator({ server: listener }); process.on('SIGINT', () => { - void (async () => { - console.debug('Closing HTTP server on signal'); - await terminator.terminate().catch(e => { console.error(e); }); - console.debug('HTTP server closed.');return undefined; - })(); - }); + void (async () => { + console.debug('Closing HTTP server on signal'); + await terminator.terminate().catch(e => { console.error(e); }); + console.debug('HTTP server closed.');return undefined; + })(); + }); } function start() { @@ -597,7 +598,7 @@ function start() { if (serviceKey && certificate) { createHttpsServer({ serviceKey: serviceKey, - certificate: certificate + certificate: certificate, }); } else { console.warn('Disabling certificate validation and proceeding with a self-signed certificate. If this is a production server, it is recommended that you configure a valid certificate instead.'); @@ -606,7 +607,7 @@ function start() { createCertificate({ days: 1, - selfSigned: true + selfSigned: true, }, (error, keys) => { createHttpsServer(keys); }); @@ -627,7 +628,7 @@ function healthCheck(req, res) { }) .catch((error) => { res.status(error.response.status).send({ - error: error.message + error: error.message, }); }); } diff --git a/src/app/access-control/access-control-routes.ts b/src/app/access-control/access-control-routes.ts new file mode 100644 index 00000000000..a7cce461ef0 --- /dev/null +++ b/src/app/access-control/access-control-routes.ts @@ -0,0 +1,117 @@ +import { AbstractControl } from '@angular/forms'; +import { + mapToCanActivate, + Route, +} from '@angular/router'; +import { + DYNAMIC_ERROR_MESSAGES_MATCHER, + DynamicErrorMessagesMatcher, +} from '@ng-dynamic-forms/core'; + +import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { GroupAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; +import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { + EPERSON_PATH, + GROUP_PATH, +} from './access-control-routing-paths'; +import { BulkAccessComponent } from './bulk-access/bulk-access.component'; +import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component'; +import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component'; +import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; +import { GroupFormComponent } from './group-registry/group-form/group-form.component'; +import { GroupPageGuard } from './group-registry/group-page.guard'; +import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; + +/** + * Condition for displaying error messages on email form field + */ +export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = + (control: AbstractControl, model: any, hasFocus: boolean) => { + return (control.touched && !hasFocus) || (control.errors?.emailTaken && hasFocus); + }; + +const providers = [ + { + provide: DYNAMIC_ERROR_MESSAGES_MATCHER, + useValue: ValidateEmailErrorStateMatcher, + }, +]; +export const ROUTES: Route[] = [ + { + path: EPERSON_PATH, + component: EPeopleRegistryComponent, + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + providers, + data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' }, + canActivate: mapToCanActivate([SiteAdministratorGuard]), + }, + { + path: `${EPERSON_PATH}/create`, + component: EPersonFormComponent, + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + providers, + data: { title: 'admin.access-control.epeople.add.title', breadcrumbKey: 'admin.access-control.epeople.add' }, + canActivate: mapToCanActivate([SiteAdministratorGuard]), + }, + { + path: `${EPERSON_PATH}/:id/edit`, + component: EPersonFormComponent, + resolve: { + breadcrumb: i18nBreadcrumbResolver, + ePerson: EPersonResolver, + }, + providers, + data: { title: 'admin.access-control.epeople.edit.title', breadcrumbKey: 'admin.access-control.epeople.edit' }, + canActivate: mapToCanActivate([SiteAdministratorGuard]), + }, + { + path: GROUP_PATH, + component: GroupsRegistryComponent, + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + providers, + data: { title: 'admin.access-control.groups.title', breadcrumbKey: 'admin.access-control.groups' }, + canActivate: mapToCanActivate([GroupAdministratorGuard]), + }, + { + path: `${GROUP_PATH}/create`, + component: GroupFormComponent, + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + providers, + data: { + title: 'admin.access-control.groups.title.addGroup', + breadcrumbKey: 'admin.access-control.groups.addGroup', + }, + canActivate: mapToCanActivate([GroupAdministratorGuard]), + }, + { + path: `${GROUP_PATH}/:groupId/edit`, + component: GroupFormComponent, + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + providers, + data: { + title: 'admin.access-control.groups.title.singleGroup', + breadcrumbKey: 'admin.access-control.groups.singleGroup', + }, + canActivate: mapToCanActivate([GroupPageGuard]), + }, + { + path: 'bulk-access', + component: BulkAccessComponent, + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + data: { title: 'admin.access-control.bulk-access.title', breadcrumbKey: 'admin.access-control.bulk-access' }, + canActivate: mapToCanActivate([SiteAdministratorGuard]), + }, +]; diff --git a/src/app/access-control/access-control-routing.module.ts b/src/app/access-control/access-control-routing.module.ts deleted file mode 100644 index e85961fd138..00000000000 --- a/src/app/access-control/access-control-routing.module.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { GroupAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; -import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; -import { - EPERSON_PATH, - GROUP_PATH, -} from './access-control-routing-paths'; -import { BulkAccessComponent } from './bulk-access/bulk-access.component'; -import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component'; -import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component'; -import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; -import { GroupFormComponent } from './group-registry/group-form/group-form.component'; -import { GroupPageGuard } from './group-registry/group-page.guard'; -import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { - path: EPERSON_PATH, - component: EPeopleRegistryComponent, - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: `${EPERSON_PATH}/create`, - component: EPersonFormComponent, - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - data: { title: 'admin.access-control.epeople.add.title', breadcrumbKey: 'admin.access-control.epeople.add' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: `${EPERSON_PATH}/:id/edit`, - component: EPersonFormComponent, - resolve: { - breadcrumb: I18nBreadcrumbResolver, - ePerson: EPersonResolver, - }, - data: { title: 'admin.access-control.epeople.edit.title', breadcrumbKey: 'admin.access-control.epeople.edit' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: GROUP_PATH, - component: GroupsRegistryComponent, - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - data: { title: 'admin.access-control.groups.title', breadcrumbKey: 'admin.access-control.groups' }, - canActivate: [GroupAdministratorGuard], - }, - { - path: `${GROUP_PATH}/create`, - component: GroupFormComponent, - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - data: { title: 'admin.access-control.groups.title.addGroup', breadcrumbKey: 'admin.access-control.groups.addGroup' }, - canActivate: [GroupAdministratorGuard], - }, - { - path: `${GROUP_PATH}/:groupId/edit`, - component: GroupFormComponent, - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - data: { title: 'admin.access-control.groups.title.singleGroup', breadcrumbKey: 'admin.access-control.groups.singleGroup' }, - canActivate: [GroupPageGuard], - }, - { - path: 'bulk-access', - component: BulkAccessComponent, - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - data: { title: 'admin.access-control.bulk-access.title', breadcrumbKey: 'admin.access-control.bulk-access' }, - canActivate: [SiteAdministratorGuard], - }, - ]), - ], -}) -/** - * Routing module for the AccessControl section of the admin sidebar - */ -export class AccessControlRoutingModule { - -} diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts deleted file mode 100644 index 87737987e08..00000000000 --- a/src/app/access-control/access-control.module.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { AbstractControl } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; -import { - DYNAMIC_ERROR_MESSAGES_MATCHER, - DynamicErrorMessagesMatcher, -} from '@ng-dynamic-forms/core'; - -import { AccessControlFormModule } from '../shared/access-control-form-container/access-control-form.module'; -import { FormModule } from '../shared/form/form.module'; -import { SearchModule } from '../shared/search/search.module'; -import { SharedModule } from '../shared/shared.module'; -import { AccessControlRoutingModule } from './access-control-routing.module'; -import { BulkAccessBrowseComponent } from './bulk-access/browse/bulk-access-browse.component'; -import { BulkAccessComponent } from './bulk-access/bulk-access.component'; -import { BulkAccessSettingsComponent } from './bulk-access/settings/bulk-access-settings.component'; -import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component'; -import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component'; -import { GroupFormComponent } from './group-registry/group-form/group-form.component'; -import { MembersListComponent } from './group-registry/group-form/members-list/members-list.component'; -import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component'; -import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; - -/** - * Condition for displaying error messages on email form field - */ -export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = - (control: AbstractControl, model: any, hasFocus: boolean) => { - return (control.touched && !hasFocus) || (control.errors?.emailTaken && hasFocus); - }; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule, - AccessControlRoutingModule, - FormModule, - NgbAccordionModule, - SearchModule, - AccessControlFormModule, - ], - exports: [ - MembersListComponent, - ], - declarations: [ - EPeopleRegistryComponent, - EPersonFormComponent, - GroupsRegistryComponent, - GroupFormComponent, - SubgroupsListComponent, - MembersListComponent, - BulkAccessComponent, - BulkAccessBrowseComponent, - BulkAccessSettingsComponent, - ], - providers: [ - { - provide: DYNAMIC_ERROR_MESSAGES_MATCHER, - useValue: ValidateEmailErrorStateMatcher, - }, - ], -}) -/** - * This module handles all components related to the access control pages - */ -export class AccessControlModule { - -} diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts index e99c25a195e..f9eb487d73a 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts @@ -13,9 +13,15 @@ import { of } from 'rxjs'; import { buildPaginatedList } from '../../../core/data/paginated-list.model'; import { PageInfo } from '../../../core/shared/page-info.model'; +import { getMockThemeService } from '../../../shared/mocks/theme-service.mock'; +import { ListableObjectComponentLoaderComponent } from '../../../shared/object-collection/shared/listable-object/listable-object-component-loader.component'; +import { SelectableListItemControlComponent } from '../../../shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component'; import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; import { SelectableObject } from '../../../shared/object-list/selectable-list/selectable-list.service.spec'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { ThemedSearchComponent } from '../../../shared/search/themed-search.component'; +import { ThemeService } from '../../../shared/theme-support/theme.service'; import { BulkAccessBrowseComponent } from './bulk-access-browse.component'; describe('BulkAccessBrowseComponent', () => { @@ -29,7 +35,7 @@ describe('BulkAccessBrowseComponent', () => { const selected1 = new SelectableObject(value1); const selected2 = new SelectableObject(value2); - const testSelection = { id: listID1, selection: [selected1, selected2] } ; + const testSelection = { id: listID1, selection: [selected1, selected2] }; const selectableListService = jasmine.createSpyObj('SelectableListService', ['getSelectableList', 'deselectAll']); beforeEach(waitForAsync(() => { @@ -38,13 +44,27 @@ describe('BulkAccessBrowseComponent', () => { NgbAccordionModule, NgbNavModule, TranslateModule.forRoot(), + BulkAccessBrowseComponent, + ], + providers: [ + { provide: SelectableListService, useValue: selectableListService }, + { provide: ThemeService, useValue: getMockThemeService() }, ], - declarations: [BulkAccessBrowseComponent], - providers: [ { provide: SelectableListService, useValue: selectableListService } ], schemas: [ NO_ERRORS_SCHEMA, ], - }).compileComponents(); + }) + .overrideComponent(BulkAccessBrowseComponent, { + remove: { + imports: [ + PaginationComponent, + ThemedSearchComponent, + SelectableListItemControlComponent, + ListableObjectComponentLoaderComponent, + ], + }, + }) + .compileComponents(); })); beforeEach(() => { @@ -79,7 +99,7 @@ describe('BulkAccessBrowseComponent', () => { 'totalElements': 2, 'totalPages': 1, 'currentPage': 1, - }), [selected1, selected2]) ; + }), [selected1, selected2]); const rd = createSuccessfulRemoteDataObject(list); expect(component.objectsSelected$.value).toEqual(rd); diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts index 6b221f107e5..a400742f017 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -1,9 +1,20 @@ +import { + AsyncPipe, + NgForOf, + NgIf, +} from '@angular/common'; import { Component, Input, OnDestroy, OnInit, } from '@angular/core'; +import { + NgbAccordionModule, + NgbNavModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgxPaginationModule } from 'ngx-pagination'; import { BehaviorSubject, Subscription, @@ -20,13 +31,18 @@ import { import { RemoteData } from '../../../core/data/remote-data'; import { PageInfo } from '../../../core/shared/page-info.model'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; -import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service'; import { hasValue } from '../../../shared/empty.util'; import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; +import { ListableObjectComponentLoaderComponent } from '../../../shared/object-collection/shared/listable-object/listable-object-component-loader.component'; +import { SelectableListItemControlComponent } from '../../../shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component'; import { SelectableListState } from '../../../shared/object-list/selectable-list/selectable-list.reducer'; import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { ThemedSearchComponent } from '../../../shared/search/themed-search.component'; +import { BrowserOnlyPipe } from '../../../shared/utils/browser-only.pipe'; @Component({ selector: 'ds-bulk-access-browse', @@ -38,6 +54,21 @@ import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.ut useClass: SearchConfigurationService, }, ], + imports: [ + PaginationComponent, + AsyncPipe, + NgbAccordionModule, + TranslateModule, + NgIf, + NgbNavModule, + ThemedSearchComponent, + BrowserOnlyPipe, + NgForOf, + NgxPaginationModule, + SelectableListItemControlComponent, + ListableObjectComponentLoaderComponent, + ], + standalone: true, }) export class BulkAccessBrowseComponent implements OnInit, OnDestroy { diff --git a/src/app/access-control/bulk-access/bulk-access.component.spec.ts b/src/app/access-control/bulk-access/bulk-access.component.spec.ts index e7ec28c132b..8bfbe1fe5d1 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.spec.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.spec.ts @@ -9,12 +9,15 @@ import { of } from 'rxjs'; import { Process } from '../../process-page/processes/process.model'; import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; +import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { ThemeService } from '../../shared/theme-support/theme.service'; import { BulkAccessComponent } from './bulk-access.component'; +import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.component'; describe('BulkAccessComponent', () => { let component: BulkAccessComponent; @@ -74,15 +77,23 @@ describe('BulkAccessComponent', () => { imports: [ RouterTestingModule, TranslateModule.forRoot(), + BulkAccessComponent, ], - declarations: [ BulkAccessComponent ], providers: [ { provide: BulkAccessControlService, useValue: bulkAccessControlServiceMock }, { provide: NotificationsService, useValue: NotificationsServiceStub }, { provide: SelectableListService, useValue: selectableListServiceMock }, + { provide: ThemeService, useValue: getMockThemeService() }, ], schemas: [NO_ERRORS_SCHEMA], }) + .overrideComponent(BulkAccessComponent, { + remove: { + imports: [ + BulkAccessSettingsComponent, + ], + }, + }) .compileComponents(); }); diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts index 52faa97dbc7..bd8e893b599 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -3,6 +3,7 @@ import { OnInit, ViewChild, } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; import { BehaviorSubject, Subscription, @@ -15,12 +16,19 @@ import { import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; +import { BulkAccessBrowseComponent } from './browse/bulk-access-browse.component'; import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.component'; @Component({ selector: 'ds-bulk-access', templateUrl: './bulk-access.component.html', styleUrls: ['./bulk-access.component.scss'], + imports: [ + TranslateModule, + BulkAccessSettingsComponent, + BulkAccessBrowseComponent, + ], + standalone: true, }) export class BulkAccessComponent implements OnInit { diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts index f3c1cad04ab..880e1f2472c 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts @@ -6,6 +6,7 @@ import { import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; +import { AccessControlFormContainerComponent } from '../../../shared/access-control-form-container/access-control-form-container.component'; import { BulkAccessSettingsComponent } from './bulk-access-settings.component'; describe('BulkAccessSettingsComponent', () => { @@ -45,10 +46,13 @@ describe('BulkAccessSettingsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [NgbAccordionModule, TranslateModule.forRoot()], - declarations: [BulkAccessSettingsComponent], + imports: [NgbAccordionModule, TranslateModule.forRoot(), BulkAccessSettingsComponent], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(BulkAccessSettingsComponent, { + remove: { imports: [AccessControlFormContainerComponent] }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts index d0f95377eb2..264cefc7084 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts @@ -1,7 +1,10 @@ +import { NgIf } from '@angular/common'; import { Component, ViewChild, } from '@angular/core'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; import { AccessControlFormContainerComponent } from '../../../shared/access-control-form-container/access-control-form-container.component'; @@ -10,6 +13,13 @@ import { AccessControlFormContainerComponent } from '../../../shared/access-cont templateUrl: 'bulk-access-settings.component.html', styleUrls: ['./bulk-access-settings.component.scss'], exportAs: 'dsBulkSettings', + imports: [ + NgbAccordionModule, + TranslateModule, + NgIf, + AccessControlFormContainerComponent, + ], + standalone: true, }) export class BulkAccessSettingsComponent { diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts index ee4d5fa603e..c636b72d561 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts @@ -19,6 +19,7 @@ import { By, } from '@angular/platform-browser'; import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; import { NgbModal, NgbModule, @@ -42,8 +43,11 @@ import { EPerson } from '../../core/eperson/models/eperson.model'; import { PaginationService } from '../../core/pagination/pagination.service'; import { PageInfo } from '../../core/shared/page-info.model'; import { FormBuilderService } from '../../shared/form/builder/form-builder.service'; +import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; import { getMockFormBuilderService } from '../../shared/mocks/form-builder-service.mock'; +import { RouterMock } from '../../shared/mocks/router.mock'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../shared/pagination/pagination.component'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { EPersonMock, @@ -51,8 +55,8 @@ import { } from '../../shared/testing/eperson.mock'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; -import { RouterStub } from '../../shared/testing/router.stub'; import { EPeopleRegistryComponent } from './epeople-registry.component'; +import { EPersonFormComponent } from './eperson-form/eperson-form.component'; describe('EPeopleRegistryComponent', () => { let component: EPeopleRegistryComponent; @@ -145,22 +149,30 @@ describe('EPeopleRegistryComponent', () => { builderService = getMockFormBuilderService(); paginationService = new PaginationServiceStub(); - await TestBed.configureTestingModule({ - imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, - TranslateModule.forRoot(), - ], - declarations: [EPeopleRegistryComponent], + TestBed.configureTestingModule({ + imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, RouterTestingModule.withRoutes([]), + TranslateModule.forRoot(), EPeopleRegistryComponent], providers: [ { provide: EPersonDataService, useValue: ePersonDataServiceStub }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: AuthorizationDataService, useValue: authorizationService }, { provide: FormBuilderService, useValue: builderService }, - { provide: Router, useValue: new RouterStub() }, + { provide: Router, useValue: new RouterMock() }, { provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) }, { provide: PaginationService, useValue: paginationService }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(EPeopleRegistryComponent, { + remove: { + imports: [ + EPersonFormComponent, + ThemedLoadingComponent, + PaginationComponent, + ], + }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.ts b/src/app/access-control/epeople-registry/epeople-registry.component.ts index ddf5fe7bfb8..5466ed0152f 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.ts @@ -1,12 +1,27 @@ +import { + AsyncPipe, + NgClass, + NgForOf, + NgIf, +} from '@angular/common'; import { Component, OnDestroy, OnInit, } from '@angular/core'; -import { UntypedFormBuilder } from '@angular/forms'; -import { Router } from '@angular/router'; +import { + ReactiveFormsModule, + UntypedFormBuilder, +} from '@angular/forms'; +import { + Router, + RouterModule, +} from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { BehaviorSubject, combineLatest, @@ -40,16 +55,32 @@ import { import { PageInfo } from '../../core/shared/page-info.model'; import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; import { hasValue } from '../../shared/empty.util'; +import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { getEPersonEditRoute, getEPersonsRoute, } from '../access-control-routing-paths'; +import { EPersonFormComponent } from './eperson-form/eperson-form.component'; @Component({ selector: 'ds-epeople-registry', templateUrl: './epeople-registry.component.html', + imports: [ + TranslateModule, + RouterModule, + AsyncPipe, + NgIf, + EPersonFormComponent, + ReactiveFormsModule, + ThemedLoadingComponent, + PaginationComponent, + NgClass, + NgForOf, + ], + standalone: true, }) /** * A component used for managing all existing epeople within the repository. diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index 5196eee631a..7c03b9eab0a 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -46,9 +46,12 @@ import { EPerson } from '../../../core/eperson/models/eperson.model'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PageInfo } from '../../../core/shared/page-info.model'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; +import { FormComponent } from '../../../shared/form/form.component'; +import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component'; import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; import { AuthServiceStub } from '../../../shared/testing/auth-service.stub'; @@ -231,8 +234,6 @@ describe('EPersonFormComponent', () => { useClass: TranslateLoaderMock, }, }), - ], - declarations: [ EPersonFormComponent, HasNoValuePipe, ], @@ -251,7 +252,11 @@ describe('EPersonFormComponent', () => { EPeopleRegistryComponent, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(EPersonFormComponent, { + remove: { imports: [ ThemedLoadingComponent, PaginationComponent,FormComponent] }, + }) + .compileComponents(); })); epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index 7eb743f1ab1..05efde7cf7d 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -1,3 +1,9 @@ +import { + AsyncPipe, + NgClass, + NgFor, + NgIf, +} from '@angular/common'; import { ChangeDetectorRef, Component, @@ -10,6 +16,7 @@ import { UntypedFormGroup } from '@angular/forms'; import { ActivatedRoute, Router, + RouterLink, } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { @@ -18,7 +25,10 @@ import { DynamicFormLayout, DynamicInputModel, } from '@ng-dynamic-forms/core'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { combineLatest as observableCombineLatest, Observable, @@ -58,15 +68,32 @@ import { TYPE_REQUEST_FORGOT } from '../../../register-email-form/register-email import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; import { hasValue } from '../../../shared/empty.util'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; +import { FormComponent } from '../../../shared/form/form.component'; +import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { followLink } from '../../../shared/utils/follow-link-config.model'; +import { HasNoValuePipe } from '../../../shared/utils/has-no-value.pipe'; import { getEPersonsRoute } from '../../access-control-routing-paths'; import { ValidateEmailNotTaken } from './validators/email-taken.validator'; @Component({ selector: 'ds-eperson-form', templateUrl: './eperson-form.component.html', + imports: [ + FormComponent, + NgIf, + NgFor, + AsyncPipe, + TranslateModule, + NgClass, + ThemedLoadingComponent, + PaginationComponent, + RouterLink, + HasNoValuePipe, + ], + standalone: true, }) /** * A form used for creating and editing EPeople diff --git a/src/app/access-control/epeople-registry/eperson-resolver.service.ts b/src/app/access-control/epeople-registry/eperson-resolver.service.ts index 33233ba7e8c..6c9d7347f73 100644 --- a/src/app/access-control/epeople-registry/eperson-resolver.service.ts +++ b/src/app/access-control/epeople-registry/eperson-resolver.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, - Resolve, RouterStateSnapshot, } from '@angular/router'; import { Store } from '@ngrx/store'; @@ -27,7 +26,7 @@ export const EPERSON_EDIT_FOLLOW_LINKS: FollowLinkConfig[] = [ @Injectable({ providedIn: 'root', }) -export class EPersonResolver implements Resolve> { +export class EPersonResolver { constructor( protected ePersonService: EPersonDataService, diff --git a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts index aca6da2a747..02de06f4155 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts @@ -53,7 +53,11 @@ import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; import { NoContent } from '../../../core/shared/NoContent.model'; import { PageInfo } from '../../../core/shared/page-info.model'; import { UUIDService } from '../../../core/shared/uuid.service'; +import { XSRFService } from '../../../core/xsrf/xsrf.service'; +import { AlertComponent } from '../../../shared/alert/alert.component'; +import { ContextHelpDirective } from '../../../shared/context-help.directive'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; +import { FormComponent } from '../../../shared/form/form.component'; import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock'; import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; import { RouterMock } from '../../../shared/mocks/router.mock'; @@ -67,6 +71,8 @@ import { import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mock'; import { GroupFormComponent } from './group-form.component'; +import { MembersListComponent } from './members-list/members-list.component'; +import { SubgroupsListComponent } from './subgroup-list/subgroups-list.component'; import { ValidateGroupExists } from './validators/group-exists.validator'; describe('GroupFormComponent', () => { @@ -227,9 +233,7 @@ describe('GroupFormComponent', () => { provide: TranslateLoader, useClass: TranslateLoaderMock, }, - }), - ], - declarations: [GroupFormComponent], + }), GroupFormComponent], providers: [ { provide: DSONameService, useValue: new DSONameServiceMock() }, { provide: EPersonDataService, useValue: ePersonDataServiceStub }, @@ -241,6 +245,7 @@ describe('GroupFormComponent', () => { { provide: HttpClient, useValue: {} }, { provide: ObjectCacheService, useValue: {} }, { provide: UUIDService, useValue: {} }, + { provide: XSRFService, useValue: {} }, { provide: Store, useValue: {} }, { provide: RemoteDataBuildService, useValue: {} }, { provide: HALEndpointService, useValue: {} }, @@ -252,7 +257,17 @@ describe('GroupFormComponent', () => { { provide: AuthorizationDataService, useValue: authorizationService }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(GroupFormComponent, { + remove: { imports: [ + FormComponent, + AlertComponent, + ContextHelpDirective, + MembersListComponent, + SubgroupsListComponent, + ] }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/access-control/group-registry/group-form/group-form.component.ts b/src/app/access-control/group-registry/group-form/group-form.component.ts index 89bb4c395fb..9fcbcbfeff6 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.ts @@ -1,3 +1,7 @@ +import { + AsyncPipe, + NgIf, +} from '@angular/common'; import { ChangeDetectorRef, Component, @@ -19,7 +23,10 @@ import { DynamicInputModel, DynamicTextAreaModel, } from '@ng-dynamic-forms/core'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { Operation } from 'fast-json-patch'; import { combineLatest as observableCombineLatest, @@ -59,25 +66,41 @@ import { getFirstSucceededRemoteDataPayload, getRemoteDataPayload, } from '../../../core/shared/operators'; +import { AlertComponent } from '../../../shared/alert/alert.component'; import { AlertType } from '../../../shared/alert/alert-type'; import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; +import { ContextHelpDirective } from '../../../shared/context-help.directive'; import { hasValue, hasValueOperator, isNotEmpty, } from '../../../shared/empty.util'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; +import { FormComponent } from '../../../shared/form/form.component'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { followLink } from '../../../shared/utils/follow-link-config.model'; import { getGroupEditRoute, getGroupsRoute, } from '../../access-control-routing-paths'; +import { MembersListComponent } from './members-list/members-list.component'; +import { SubgroupsListComponent } from './subgroup-list/subgroups-list.component'; import { ValidateGroupExists } from './validators/group-exists.validator'; @Component({ selector: 'ds-group-form', templateUrl: './group-form.component.html', + imports: [ + FormComponent, + AlertComponent, + NgIf, + AsyncPipe, + TranslateModule, + ContextHelpDirective, + MembersListComponent, + SubgroupsListComponent, + ], + standalone: true, }) /** * A form used for creating and editing groups diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts index c63ff40df8f..9cc77275579 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts @@ -20,7 +20,10 @@ import { BrowserModule, By, } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { + ActivatedRoute, + Router, +} from '@angular/router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateLoader, @@ -45,13 +48,16 @@ import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { Group } from '../../../../core/eperson/models/group.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PageInfo } from '../../../../core/shared/page-info.model'; +import { ContextHelpDirective } from '../../../../shared/context-help.directive'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { DSONameServiceMock } from '../../../../shared/mocks/dso-name.service.mock'; import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; import { RouterMock } from '../../../../shared/mocks/router.mock'; import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { ActivatedRouteStub } from '../../../../shared/testing/active-router.stub'; import { EPersonMock, EPersonMock2, @@ -155,9 +161,7 @@ describe('MembersListComponent', () => { provide: TranslateLoader, useClass: TranslateLoaderMock, }, - }), - ], - declarations: [MembersListComponent], + }), MembersListComponent], providers: [MembersListComponent, { provide: EPersonDataService, useValue: ePersonDataServiceStub }, { provide: GroupDataService, useValue: groupsDataServiceStub }, @@ -166,9 +170,16 @@ describe('MembersListComponent', () => { { provide: Router, useValue: new RouterMock() }, { provide: PaginationService, useValue: paginationService }, { provide: DSONameService, useValue: new DSONameServiceMock() }, + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(MembersListComponent, { + remove: { + imports: [PaginationComponent, ContextHelpDirective], + }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts index 002e20524c3..92f764fe98a 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts @@ -1,12 +1,27 @@ +import { + AsyncPipe, + NgClass, + NgForOf, + NgIf, +} from '@angular/common'; import { Component, Input, OnDestroy, OnInit, } from '@angular/core'; -import { UntypedFormBuilder } from '@angular/forms'; -import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + ReactiveFormsModule, + UntypedFormBuilder, +} from '@angular/forms'; +import { + Router, + RouterLink, +} from '@angular/router'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { BehaviorSubject, Observable, @@ -31,7 +46,9 @@ import { getFirstCompletedRemoteData, getRemoteDataPayload, } from '../../../../core/shared/operators'; +import { ContextHelpDirective } from '../../../../shared/context-help.directive'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; import { getEPersonEditRoute } from '../../../access-control-routing-paths'; @@ -78,6 +95,18 @@ export interface EPersonListActionConfig { @Component({ selector: 'ds-members-list', templateUrl: './members-list.component.html', + imports: [ + TranslateModule, + ContextHelpDirective, + ReactiveFormsModule, + PaginationComponent, + NgIf, + AsyncPipe, + RouterLink, + NgClass, + NgForOf, + ], + standalone: true, }) /** * The list of members in the edit group page diff --git a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts index ca50406aead..5b39102ca8a 100644 --- a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts @@ -19,7 +19,10 @@ import { BrowserModule, By, } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { + ActivatedRoute, + Router, +} from '@angular/router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateLoader, @@ -43,13 +46,16 @@ import { GroupDataService } from '../../../../core/eperson/group-data.service'; import { Group } from '../../../../core/eperson/models/group.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PageInfo } from '../../../../core/shared/page-info.model'; +import { ContextHelpDirective } from '../../../../shared/context-help.directive'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { DSONameServiceMock } from '../../../../shared/mocks/dso-name.service.mock'; import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; import { RouterMock } from '../../../../shared/mocks/router.mock'; import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { ActivatedRouteStub } from '../../../../shared/testing/active-router.stub'; import { GroupMock, GroupMock2, @@ -119,7 +125,9 @@ describe('SubgroupsListComponent', () => { if (query === '') { return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupNonMembers)); } - return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])); + return createSuccessfulRemoteDataObject$( + buildPaginatedList(new PageInfo(), []), + ); }, addSubGroupToGroup(parentGroup, subgroupToAdd: Group): Observable { // Add group to list of subgroups @@ -153,28 +161,44 @@ describe('SubgroupsListComponent', () => { routerStub = new RouterMock(); builderService = getMockFormBuilderService(); translateService = getMockTranslateService(); - paginationService = new PaginationServiceStub(); return TestBed.configureTestingModule({ - imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, + imports: [ + CommonModule, + NgbModule, + FormsModule, + ReactiveFormsModule, + BrowserModule, + // ContextHelpDirective, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateLoaderMock, }, }), + SubgroupsListComponent, ], - declarations: [SubgroupsListComponent], - providers: [SubgroupsListComponent, + providers: [ + SubgroupsListComponent, { provide: DSONameService, useValue: new DSONameServiceMock() }, { provide: GroupDataService, useValue: groupsDataServiceStub }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { + provide: NotificationsService, + useValue: new NotificationsServiceStub(), + }, { provide: FormBuilderService, useValue: builderService }, { provide: Router, useValue: routerStub }, { provide: PaginationService, useValue: paginationService }, + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(SubgroupsListComponent, { + remove: { + imports: [ContextHelpDirective, PaginationComponent], + }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts index ae677a55589..fc25dd4fc99 100644 --- a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts +++ b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts @@ -1,12 +1,26 @@ +import { + AsyncPipe, + NgForOf, + NgIf, +} from '@angular/common'; import { Component, Input, OnDestroy, OnInit, } from '@angular/core'; -import { UntypedFormBuilder } from '@angular/forms'; -import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + ReactiveFormsModule, + UntypedFormBuilder, +} from '@angular/forms'; +import { + Router, + RouterLink, +} from '@angular/router'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { BehaviorSubject, Observable, @@ -30,7 +44,9 @@ import { getFirstCompletedRemoteData, } from '../../../../core/shared/operators'; import { PageInfo } from '../../../../core/shared/page-info.model'; +import { ContextHelpDirective } from '../../../../shared/context-help.directive'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; import { followLink } from '../../../../shared/utils/follow-link-config.model'; @@ -46,6 +62,17 @@ enum SubKey { @Component({ selector: 'ds-subgroups-list', templateUrl: './subgroups-list.component.html', + imports: [ + RouterLink, + AsyncPipe, + NgForOf, + ContextHelpDirective, + TranslateModule, + ReactiveFormsModule, + PaginationComponent, + NgIf, + ], + standalone: true, }) /** * The list of subgroups in the edit group page diff --git a/src/app/access-control/group-registry/groups-registry.component.spec.ts b/src/app/access-control/group-registry/groups-registry.component.spec.ts index 73bb2fe80a0..9f847faf0b0 100644 --- a/src/app/access-control/group-registry/groups-registry.component.spec.ts +++ b/src/app/access-control/group-registry/groups-registry.component.spec.ts @@ -16,8 +16,12 @@ import { BrowserModule, By, } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { + ActivatedRoute, + Router, +} from '@angular/router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { provideMockStore } from '@ngrx/store/testing'; import { TranslateLoader, TranslateModule, @@ -25,9 +29,12 @@ import { import { Observable, of as observableOf, + of, } from 'rxjs'; +import { APP_DATA_SERVICES_MAP } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { ConfigurationDataService } from '../../core/data/configuration-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; @@ -53,6 +60,7 @@ import { import { RouterMock } from '../../shared/mocks/router.mock'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { EPersonMock, EPersonMock2, @@ -74,6 +82,7 @@ describe('GroupsRegistryComponent', () => { let groupsDataServiceStub: any; let dsoDataServiceStub: any; let authorizationService: AuthorizationDataService; + let configurationDataService: jasmine.SpyObj; let mockGroups; let mockEPeople; @@ -191,6 +200,10 @@ describe('GroupsRegistryComponent', () => { }, }; + configurationDataService = jasmine.createSpyObj('ConfigurationDataService', { + findByPropertyName: of({ payload: { value: 'test' } }), + }); + authorizationService = jasmine.createSpyObj('authorizationService', ['isAuthorized']); setIsAuthorized(true, true); paginationService = new PaginationServiceStub(); @@ -201,20 +214,22 @@ describe('GroupsRegistryComponent', () => { provide: TranslateLoader, useClass: TranslateLoaderMock, }, - }), - ], - declarations: [GroupsRegistryComponent], + }), GroupsRegistryComponent], providers: [GroupsRegistryComponent, { provide: DSONameService, useValue: new DSONameServiceMock() }, { provide: EPersonDataService, useValue: ePersonDataServiceStub }, { provide: GroupDataService, useValue: groupsDataServiceStub }, { provide: DSpaceObjectDataService, useValue: dsoDataServiceStub }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: ConfigurationDataService, useValue: configurationDataService }, { provide: RouteService, useValue: routeServiceStub }, + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, { provide: Router, useValue: new RouterMock() }, { provide: AuthorizationDataService, useValue: authorizationService }, { provide: PaginationService, useValue: paginationService }, { provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, + provideMockStore(), ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/src/app/access-control/group-registry/groups-registry.component.ts b/src/app/access-control/group-registry/groups-registry.component.ts index 24d6f9e4a9f..d97284488a9 100644 --- a/src/app/access-control/group-registry/groups-registry.component.ts +++ b/src/app/access-control/group-registry/groups-registry.component.ts @@ -1,11 +1,28 @@ +import { + AsyncPipe, + NgForOf, + NgIf, + NgSwitch, + NgSwitchCase, +} from '@angular/common'; import { Component, OnDestroy, OnInit, } from '@angular/core'; -import { UntypedFormBuilder } from '@angular/forms'; -import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + ReactiveFormsModule, + UntypedFormBuilder, +} from '@angular/forms'; +import { + Router, + RouterLink, +} from '@angular/router'; +import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { BehaviorSubject, combineLatest as observableCombineLatest, @@ -49,13 +66,29 @@ import { } from '../../core/shared/operators'; import { PageInfo } from '../../core/shared/page-info.model'; import { hasValue } from '../../shared/empty.util'; +import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { followLink } from '../../shared/utils/follow-link-config.model'; @Component({ selector: 'ds-groups-registry', templateUrl: './groups-registry.component.html', + imports: [ + ThemedLoadingComponent, + TranslateModule, + RouterLink, + ReactiveFormsModule, + AsyncPipe, + NgIf, + PaginationComponent, + NgSwitch, + NgSwitchCase, + NgbTooltipModule, + NgForOf, + ], + standalone: true, }) /** * A component used for managing all existing groups within the repository. diff --git a/src/app/admin/admin-curation-tasks/admin-curation-tasks.component.spec.ts b/src/app/admin/admin-curation-tasks/admin-curation-tasks.component.spec.ts index 5aabe0fda0e..c2981f1fb6d 100644 --- a/src/app/admin/admin-curation-tasks/admin-curation-tasks.component.spec.ts +++ b/src/app/admin/admin-curation-tasks/admin-curation-tasks.component.spec.ts @@ -6,6 +6,7 @@ import { } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; +import { CurationFormComponent } from '../../curation-form/curation-form.component'; import { AdminCurationTasksComponent } from './admin-curation-tasks.component'; describe('AdminCurationTasksComponent', () => { @@ -14,10 +15,15 @@ describe('AdminCurationTasksComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [AdminCurationTasksComponent], + imports: [TranslateModule.forRoot(), AdminCurationTasksComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(AdminCurationTasksComponent, { + remove: { + imports: [CurationFormComponent], + }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/admin/admin-curation-tasks/admin-curation-tasks.component.ts b/src/app/admin/admin-curation-tasks/admin-curation-tasks.component.ts index 9a80f341b91..657f972468b 100644 --- a/src/app/admin/admin-curation-tasks/admin-curation-tasks.component.ts +++ b/src/app/admin/admin-curation-tasks/admin-curation-tasks.component.ts @@ -1,4 +1,7 @@ import { Component } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; + +import { CurationFormComponent } from '../../curation-form/curation-form.component'; /** * Component responsible for rendering the system wide Curation Task UI @@ -6,6 +9,11 @@ import { Component } from '@angular/core'; @Component({ selector: 'ds-admin-curation-task', templateUrl: './admin-curation-tasks.component.html', + imports: [ + CurationFormComponent, + TranslateModule, + ], + standalone: true, }) export class AdminCurationTasksComponent { diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts index 752bd52e4d2..20c53f58ed9 100644 --- a/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts @@ -23,6 +23,7 @@ import { createSuccessfulRemoteDataObject$, } from '../../shared/remote-data.utils'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { FileDropzoneNoUploaderComponent } from '../../shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component'; import { FileValueAccessorDirective } from '../../shared/utils/file-value-accessor.directive'; import { FileValidator } from '../../shared/utils/require-file.validator'; import { BatchImportPageComponent } from './batch-import-page.component'; @@ -58,8 +59,8 @@ describe('BatchImportPageComponent', () => { FormsModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), + BatchImportPageComponent, FileValueAccessorDirective, FileValidator, ], - declarations: [BatchImportPageComponent, FileValueAccessorDirective, FileValidator], providers: [ { provide: NotificationsService, useValue: notificationService }, { provide: ScriptDataService, useValue: scriptService }, @@ -67,7 +68,13 @@ describe('BatchImportPageComponent', () => { { provide: Location, useValue: locationStub }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(BatchImportPageComponent, { + remove: { + imports: [FileDropzoneNoUploaderComponent], + }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts index 2712610fd1a..1f54b801c8c 100644 --- a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts @@ -1,8 +1,16 @@ -import { Location } from '@angular/common'; +import { + Location, + NgIf, +} from '@angular/common'; import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; +import { UiSwitchModule } from 'ngx-ui-switch'; import { take } from 'rxjs/operators'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; @@ -22,10 +30,19 @@ import { isNotEmpty, } from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { FileDropzoneNoUploaderComponent } from '../../shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component'; @Component({ selector: 'ds-batch-import-page', templateUrl: './batch-import-page.component.html', + imports: [ + NgIf, + TranslateModule, + FormsModule, + UiSwitchModule, + FileDropzoneNoUploaderComponent, + ], + standalone: true, }) export class BatchImportPageComponent { /** diff --git a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.spec.ts b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.spec.ts index 131b4561c1c..b345da2c06f 100644 --- a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.spec.ts +++ b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.spec.ts @@ -23,6 +23,7 @@ import { createSuccessfulRemoteDataObject$, } from '../../shared/remote-data.utils'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { FileDropzoneNoUploaderComponent } from '../../shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component'; import { FileValueAccessorDirective } from '../../shared/utils/file-value-accessor.directive'; import { FileValidator } from '../../shared/utils/require-file.validator'; import { MetadataImportPageComponent } from './metadata-import-page.component'; @@ -58,8 +59,8 @@ describe('MetadataImportPageComponent', () => { FormsModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), + MetadataImportPageComponent, FileValueAccessorDirective, FileValidator, ], - declarations: [MetadataImportPageComponent, FileValueAccessorDirective, FileValidator], providers: [ { provide: NotificationsService, useValue: notificationService }, { provide: ScriptDataService, useValue: scriptService }, @@ -67,7 +68,13 @@ describe('MetadataImportPageComponent', () => { { provide: Location, useValue: locationStub }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(MetadataImportPageComponent, { + remove: { + imports: [FileDropzoneNoUploaderComponent], + }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.ts b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.ts index e5544592865..103f7c42ded 100644 --- a/src/app/admin/admin-import-metadata-page/metadata-import-page.component.ts +++ b/src/app/admin/admin-import-metadata-page/metadata-import-page.component.ts @@ -1,7 +1,11 @@ import { Location } from '@angular/common'; import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { METADATA_IMPORT_SCRIPT_NAME, @@ -14,10 +18,17 @@ import { Process } from '../../process-page/processes/process.model'; import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; import { isNotEmpty } from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { FileDropzoneNoUploaderComponent } from '../../shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component'; @Component({ selector: 'ds-metadata-import-page', templateUrl: './metadata-import-page.component.html', + imports: [ + TranslateModule, + FormsModule, + FileDropzoneNoUploaderComponent, + ], + standalone: true, }) /** diff --git a/src/app/admin/admin-ldn-services/admin-ldn-services-routes.ts b/src/app/admin/admin-ldn-services/admin-ldn-services-routes.ts new file mode 100644 index 00000000000..66420f7a7ba --- /dev/null +++ b/src/app/admin/admin-ldn-services/admin-ldn-services-routes.ts @@ -0,0 +1,38 @@ +import { Routes } from '@angular/router'; + +import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { navigationBreadcrumbResolver } from '../../core/breadcrumbs/navigation-breadcrumb.resolver'; +import { LdnServiceFormComponent } from './ldn-service-form/ldn-service-form.component'; +import { LdnServicesOverviewComponent } from './ldn-services-directory/ldn-services-directory.component'; + +const moduleRoutes: Routes = [ + { + path: '', + pathMatch: 'full', + component: LdnServicesOverviewComponent, + resolve: { breadcrumb: i18nBreadcrumbResolver }, + data: { title: 'ldn-registered-services.title', breadcrumbKey: 'ldn-registered-services.new' }, + }, + { + path: 'new', + resolve: { breadcrumb: navigationBreadcrumbResolver }, + component: LdnServiceFormComponent, + data: { title: 'ldn-register-new-service.title', breadcrumbKey: 'ldn-register-new-service' }, + }, + { + path: 'edit/:serviceId', + resolve: { breadcrumb: navigationBreadcrumbResolver }, + component: LdnServiceFormComponent, + data: { title: 'ldn-edit-service.title', breadcrumbKey: 'ldn-edit-service' }, + }, +]; + +export const ROUTES = moduleRoutes.map(route => { + return { ...route, data: { + ...route.data, + relatedRoutes: moduleRoutes.filter(relatedRoute => relatedRoute.path !== route.path) + .map((relatedRoute) => { + return { path: relatedRoute.path, data: relatedRoute.data }; + }), + } }; +}); diff --git a/src/app/admin/admin-ldn-services/admin-ldn-services-routing.module.ts b/src/app/admin/admin-ldn-services/admin-ldn-services-routing.module.ts deleted file mode 100644 index c1bdff9818e..00000000000 --- a/src/app/admin/admin-ldn-services/admin-ldn-services-routing.module.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { NgModule } from '@angular/core'; -import { - RouterModule, - Routes, -} from '@angular/router'; - -import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { NavigationBreadcrumbResolver } from '../../core/breadcrumbs/navigation-breadcrumb.resolver'; -import { LdnServiceFormComponent } from './ldn-service-form/ldn-service-form.component'; -import { LdnServicesOverviewComponent } from './ldn-services-directory/ldn-services-directory.component'; - -const moduleRoutes: Routes = [ - { - path: '', - pathMatch: 'full', - component: LdnServicesOverviewComponent, - resolve: { breadcrumb: I18nBreadcrumbResolver }, - data: { title: 'ldn-registered-services.title', breadcrumbKey: 'ldn-registered-services.new' }, - }, - { - path: 'new', - resolve: { breadcrumb: NavigationBreadcrumbResolver }, - component: LdnServiceFormComponent, - data: { title: 'ldn-register-new-service.title', breadcrumbKey: 'ldn-register-new-service' }, - }, - { - path: 'edit/:serviceId', - resolve: { breadcrumb: NavigationBreadcrumbResolver }, - component: LdnServiceFormComponent, - data: { title: 'ldn-edit-service.title', breadcrumbKey: 'ldn-edit-service' }, - }, -]; - - -@NgModule({ - imports: [ - RouterModule.forChild(moduleRoutes.map(route => { - return { ...route, data: { - ...route.data, - relatedRoutes: moduleRoutes.filter(relatedRoute => relatedRoute.path !== route.path) - .map((relatedRoute) => { - return { path: relatedRoute.path, data: relatedRoute.data }; - }), - } }; - })), - ], -}) -export class AdminLdnServicesRoutingModule { - -} diff --git a/src/app/admin/admin-ldn-services/admin-ldn-services.module.ts b/src/app/admin/admin-ldn-services/admin-ldn-services.module.ts deleted file mode 100644 index 59875158eff..00000000000 --- a/src/app/admin/admin-ldn-services/admin-ldn-services.module.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; - -import { SharedModule } from '../../shared/shared.module'; -import { AdminLdnServicesRoutingModule } from './admin-ldn-services-routing.module'; -import { LdnServiceFormComponent } from './ldn-service-form/ldn-service-form.component'; -import { LdnItemfiltersService } from './ldn-services-data/ldn-itemfilters-data.service'; -import { LdnServicesOverviewComponent } from './ldn-services-directory/ldn-services-directory.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - AdminLdnServicesRoutingModule, - FormsModule, - ], - declarations: [ - LdnServicesOverviewComponent, - LdnServiceFormComponent, - ], - providers: [LdnItemfiltersService], -}) -export class AdminLdnServicesModule { -} diff --git a/src/app/admin/admin-ldn-services/ldn-service-form/ldn-service-form.component.spec.ts b/src/app/admin/admin-ldn-services/ldn-service-form/ldn-service-form.component.spec.ts index b5d774b6c52..28f18a8a732 100644 --- a/src/app/admin/admin-ldn-services/ldn-service-form/ldn-service-form.component.spec.ts +++ b/src/app/admin/admin-ldn-services/ldn-service-form/ldn-service-form.component.spec.ts @@ -114,8 +114,7 @@ describe('LdnServiceFormEditComponent', () => { activatedRoute = new MockActivatedRoute(routeParams, routeUrlSegments); await TestBed.configureTestingModule({ - imports: [ReactiveFormsModule, TranslateModule.forRoot(), NgbDropdownModule], - declarations: [LdnServiceFormComponent], + imports: [ReactiveFormsModule, TranslateModule.forRoot(), NgbDropdownModule, LdnServiceFormComponent], providers: [ { provide: LdnServicesService, useValue: ldnServicesService }, { provide: LdnItemfiltersService, useValue: ldnItemfiltersService }, diff --git a/src/app/admin/admin-ldn-services/ldn-service-form/ldn-service-form.component.ts b/src/app/admin/admin-ldn-services/ldn-service-form/ldn-service-form.component.ts index cae913ecfdc..26b347a1752 100644 --- a/src/app/admin/admin-ldn-services/ldn-service-form/ldn-service-form.component.ts +++ b/src/app/admin/admin-ldn-services/ldn-service-form/ldn-service-form.component.ts @@ -5,6 +5,11 @@ import { transition, trigger, } from '@angular/animations'; +import { + AsyncPipe, + NgForOf, + NgIf, +} from '@angular/common'; import { ChangeDetectorRef, Component, @@ -17,14 +22,21 @@ import { FormArray, FormBuilder, FormGroup, + ReactiveFormsModule, Validators, } from '@angular/forms'; import { ActivatedRoute, Router, } from '@angular/router'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateService } from '@ngx-translate/core'; +import { + NgbDropdownModule, + NgbModal, +} from '@ng-bootstrap/ng-bootstrap'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { Operation } from 'fast-json-patch'; import { combineLatestWith, @@ -54,6 +66,7 @@ import { notifyPatterns } from '../ldn-services-patterns/ldn-service-coar-patter selector: 'ds-ldn-service-form', templateUrl: './ldn-service-form.component.html', styleUrls: ['./ldn-service-form.component.scss'], + standalone: true, animations: [ trigger('toggleAnimation', [ state('true', style({})), @@ -61,6 +74,14 @@ import { notifyPatterns } from '../ldn-services-patterns/ldn-service-coar-patter transition('true <=> false', animate('300ms ease-in')), ]), ], + imports: [ + ReactiveFormsModule, + TranslateModule, + NgIf, + NgbDropdownModule, + NgForOf, + AsyncPipe, + ], }) export class LdnServiceFormComponent implements OnInit, OnDestroy { formModel: FormGroup; diff --git a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service.ts b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service.ts index 8222c69d797..d64d2ed5cbc 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service.ts @@ -3,7 +3,6 @@ import { Observable } from 'rxjs'; import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; -import { dataService } from '../../../core/data/base/data-service.decorator'; import { FindAllData, FindAllDataImpl, @@ -16,14 +15,12 @@ import { RequestService } from '../../../core/data/request.service'; import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; -import { LDN_SERVICE_CONSTRAINT_FILTERS } from '../ldn-services-model/ldn-service.resource-type'; import { Itemfilter } from '../ldn-services-model/ldn-service-itemfilters'; /** * A service responsible for fetching/sending data from/to the REST API on the itemfilters endpoint */ -@Injectable() -@dataService(LDN_SERVICE_CONSTRAINT_FILTERS) +@Injectable({ providedIn: 'root' }) export class LdnItemfiltersService extends IdentifiableDataService implements FindAllData { private findAllData: FindAllDataImpl; diff --git a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts index a41bf0c7595..6e368dd6809 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts @@ -13,7 +13,6 @@ import { CreateData, CreateDataImpl, } from '../../../core/data/base/create-data'; -import { dataService } from '../../../core/data/base/data-service.decorator'; import { DeleteData, DeleteDataImpl, @@ -42,7 +41,6 @@ import { URLCombiner } from '../../../core/url-combiner/url-combiner'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; import { LdnServiceConstrain } from '../ldn-services-model/ldn-service.constrain.model'; -import { LDN_SERVICE } from '../ldn-services-model/ldn-service.resource-type'; import { LdnService } from '../ldn-services-model/ldn-services.model'; /** @@ -56,8 +54,7 @@ import { LdnService } from '../ldn-services-model/ldn-services.model'; * @implements {PatchData} * @implements {CreateData} */ -@Injectable() -@dataService(LDN_SERVICE) +@Injectable({ providedIn: 'root' }) export class LdnServicesService extends IdentifiableDataService implements FindAllData, DeleteData, PatchData, CreateData { createData: CreateDataImpl; private findAllData: FindAllDataImpl; diff --git a/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.spec.ts b/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.spec.ts index babab8a5ab0..fff3331e9cd 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.spec.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.spec.ts @@ -9,6 +9,7 @@ import { TestBed, tick, } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, @@ -20,10 +21,14 @@ import { PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; import { createPaginatedList } from '../../../shared/testing/utils.test'; +import { TruncatableComponent } from '../../../shared/truncatable/truncatable.component'; +import { TruncatablePartComponent } from '../../../shared/truncatable/truncatable-part/truncatable-part.component'; import { LdnServicesService } from '../ldn-services-data/ldn-services-data.service'; import { LdnService } from '../ldn-services-model/ldn-services.model'; import { LdnServicesOverviewComponent } from './ldn-services-directory.component'; @@ -50,8 +55,7 @@ describe('LdnServicesOverviewComponent', () => { 'patch': createSuccessfulRemoteDataObject$({}), }); await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [LdnServicesOverviewComponent], + imports: [TranslateModule.forRoot(), LdnServicesOverviewComponent], providers: [ { provide: LdnServicesService, @@ -60,16 +64,28 @@ describe('LdnServicesOverviewComponent', () => { { provide: PaginationService, useValue: paginationService }, { provide: NgbModal, useValue: { - open: () => { /*comment*/ + open: () => { + // }, }, }, { provide: ChangeDetectorRef, useValue: {} }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: TranslateService, useValue: translateServiceStub }, + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(LdnServicesOverviewComponent, { + remove: { + imports: [ + PaginationComponent, + TruncatableComponent, + TruncatablePartComponent, + ], + }, + }) + .compileComponents(); }); beforeEach(() => { @@ -107,11 +123,9 @@ describe('LdnServicesOverviewComponent', () => { component.ldnServicesRD$ = createSuccessfulRemoteDataObject$(mockLdnServicesRD); fixture.detectChanges(); - const tableRows = fixture.debugElement.nativeElement.querySelectorAll('tbody tr'); - expect(tableRows.length).toBe(testData.length); - const firstRowContent = tableRows[0].textContent; - expect(firstRowContent).toContain('Service 1'); - expect(firstRowContent).toContain('Description 1'); + component.ldnServicesRD$.subscribe((rd) => { + expect(rd.payload.page).toEqual(mockLdnServicesRD.page); + }); })); }); diff --git a/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.ts b/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.ts index aa85ff1cbec..cc2cba4704c 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.ts @@ -1,3 +1,9 @@ +import { + AsyncPipe, + NgClass, + NgFor, + NgIf, +} from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -7,8 +13,12 @@ import { TemplateRef, ViewChild, } from '@angular/core'; +import { RouterLink } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { Operation } from 'fast-json-patch'; import { Observable, @@ -27,7 +37,10 @@ import { RemoteData } from '../../../core/data/remote-data'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { TruncatableComponent } from '../../../shared/truncatable/truncatable.component'; +import { TruncatablePartComponent } from '../../../shared/truncatable/truncatable-part/truncatable-part.component'; import { LdnService } from '../ldn-services-model/ldn-services.model'; /** @@ -40,6 +53,18 @@ import { LdnService } from '../ldn-services-model/ldn-services.model'; templateUrl: './ldn-services-directory.component.html', styleUrls: ['./ldn-services-directory.component.scss'], changeDetection: ChangeDetectionStrategy.Default, + imports: [ + NgIf, + NgFor, + TranslateModule, + AsyncPipe, + PaginationComponent, + TruncatableComponent, + TruncatablePartComponent, + NgClass, + RouterLink, + ], + standalone: true, }) export class LdnServicesOverviewComponent implements OnInit, OnDestroy { diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts index 4d988e69085..15d384a1047 100644 --- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, - Resolve, RouterStateSnapshot, } from '@angular/router'; @@ -17,8 +16,8 @@ export interface NotificationsSuggestionTargetsPageParams { /** * This class represents a resolver that retrieve the route data before the route is activated. */ -@Injectable() -export class NotificationsSuggestionTargetsPageResolver implements Resolve { +@Injectable({ providedIn: 'root' }) +export class NotificationsSuggestionTargetsPageResolver { /** * Method for resolving the parameters in the current route. diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts index 0dcee41450d..ce8e8469009 100644 --- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts @@ -1,37 +1,40 @@ import { CommonModule } from '@angular/common'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { - async, ComponentFixture, TestBed, + waitForAsync, } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; -import { NotificationsSuggestionTargetsPageComponent } from '../../../quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component'; +import { PublicationClaimComponent } from '../../../notifications/suggestion-targets/publication-claim/publication-claim.component'; +import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page.component'; -describe('NotificationsSuggestionTargetsPageComponent', () => { - let component: NotificationsSuggestionTargetsPageComponent; - let fixture: ComponentFixture; +describe('AdminNotificationsPublicationClaimPageComponent', () => { + let component: AdminNotificationsPublicationClaimPageComponent; + let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ CommonModule, TranslateModule.forRoot(), - ], - declarations: [ - NotificationsSuggestionTargetsPageComponent, + AdminNotificationsPublicationClaimPageComponent, ], providers: [ - NotificationsSuggestionTargetsPageComponent, + AdminNotificationsPublicationClaimPageComponent, ], schemas: [NO_ERRORS_SCHEMA], + }).overrideComponent(AdminNotificationsPublicationClaimPageComponent, { + remove: { + imports: [PublicationClaimComponent], + }, }) .compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(NotificationsSuggestionTargetsPageComponent); + fixture = TestBed.createComponent(AdminNotificationsPublicationClaimPageComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts index c25fc43d455..24af9350ee9 100644 --- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts @@ -1,9 +1,15 @@ import { Component } from '@angular/core'; +import { PublicationClaimComponent } from '../../../notifications/suggestion-targets/publication-claim/publication-claim.component'; + @Component({ selector: 'ds-admin-notifications-publication-claim-page', templateUrl: './admin-notifications-publication-claim-page.component.html', styleUrls: ['./admin-notifications-publication-claim-page.component.scss'], + imports: [ + PublicationClaimComponent, + ], + standalone: true, }) export class AdminNotificationsPublicationClaimPageComponent { diff --git a/src/app/admin/admin-notifications/admin-notifications-routes.ts b/src/app/admin/admin-notifications/admin-notifications-routes.ts new file mode 100644 index 00000000000..43cfc2945a6 --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications-routes.ts @@ -0,0 +1,98 @@ +import { Route } from '@angular/router'; + +import { authenticatedGuard } from '../../core/auth/authenticated.guard'; +import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { qualityAssuranceBreadcrumbResolver } from '../../core/breadcrumbs/quality-assurance-breadcrumb.resolver'; +import { AdminNotificationsPublicationClaimPageResolver } from '../../quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service'; +import { QualityAssuranceEventsPageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.component'; +import { qualityAssuranceEventsPageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.resolver'; +import { qualityAssuranceSourceDataResolver } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver'; +import { QualityAssuranceSourcePageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-page.component'; +import { QualityAssuranceSourcePageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-page-resolver.service'; +import { QualityAssuranceTopicsPageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-topics-page/quality-assurance-topics-page.component'; +import { QualityAssuranceTopicsPageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-topics-page/quality-assurance-topics-page-resolver.service'; +import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component'; +import { + PUBLICATION_CLAIMS_PATH, + QUALITY_ASSURANCE_EDIT_PATH, +} from './admin-notifications-routing-paths'; + +export const ROUTES: Route[] = [ + { + canActivate: [ authenticatedGuard ], + path: `${PUBLICATION_CLAIMS_PATH}`, + component: AdminNotificationsPublicationClaimPageComponent, + pathMatch: 'full', + resolve: { + breadcrumb: i18nBreadcrumbResolver, + suggestionTargetParams: AdminNotificationsPublicationClaimPageResolver, + }, + data: { + title: 'admin.notifications.publicationclaim.page.title', + breadcrumbKey: 'admin.notifications.publicationclaim', + showBreadcrumbsFluid: false, + }, + }, + { + canActivate: [authenticatedGuard], + path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId`, + component: QualityAssuranceTopicsPageComponent, + pathMatch: 'full', + resolve: { + breadcrumb: qualityAssuranceBreadcrumbResolver, + openaireQualityAssuranceTopicsParams: QualityAssuranceTopicsPageResolver, + }, + data: { + title: 'admin.quality-assurance.page.title', + breadcrumbKey: 'admin.quality-assurance', + showBreadcrumbsFluid: false, + }, + }, + { + canActivate: [ authenticatedGuard ], + path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/target/:targetId`, + component: QualityAssuranceTopicsPageComponent, + pathMatch: 'full', + resolve: { + breadcrumb: i18nBreadcrumbResolver, + openaireQualityAssuranceTopicsParams: QualityAssuranceTopicsPageResolver, + }, + data: { + title: 'admin.quality-assurance.page.title', + breadcrumbKey: 'admin.quality-assurance', + showBreadcrumbsFluid: false, + }, + }, + { + canActivate: [authenticatedGuard], + path: `${QUALITY_ASSURANCE_EDIT_PATH}`, + component: QualityAssuranceSourcePageComponent, + pathMatch: 'full', + resolve: { + breadcrumb: i18nBreadcrumbResolver, + openaireQualityAssuranceSourceParams: QualityAssuranceSourcePageResolver, + sourceData: qualityAssuranceSourceDataResolver, + }, + data: { + title: 'admin.notifications.source.breadcrumbs', + breadcrumbKey: 'admin.notifications.source', + showBreadcrumbsFluid: false, + }, + }, + { + canActivate: [authenticatedGuard], + path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/:topicId`, + component: QualityAssuranceEventsPageComponent, + pathMatch: 'full', + resolve: { + breadcrumb: qualityAssuranceBreadcrumbResolver, + openaireQualityAssuranceEventsParams: qualityAssuranceEventsPageResolver, + }, + data: { + title: 'admin.notifications.event.page.title', + breadcrumbKey: 'admin.notifications.event', + showBreadcrumbsFluid: false, + }, + }, +]; + diff --git a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts deleted file mode 100644 index abcc6587ddb..00000000000 --- a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { AuthenticatedGuard } from '../../core/auth/authenticated.guard'; -import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service'; -import { QualityAssuranceBreadcrumbResolver } from '../../core/breadcrumbs/quality-assurance-breadcrumb.resolver'; -import { QualityAssuranceBreadcrumbService } from '../../core/breadcrumbs/quality-assurance-breadcrumb.service'; -import { SiteAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; -import { AdminNotificationsPublicationClaimPageResolver } from '../../quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service'; -import { QualityAssuranceEventsPageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.component'; -import { QualityAssuranceEventsPageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.resolver'; -import { SourceDataResolver } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver'; -import { QualityAssuranceSourcePageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-page.component'; -import { QualityAssuranceSourcePageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-page-resolver.service'; -import { QualityAssuranceTopicsPageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-topics-page/quality-assurance-topics-page.component'; -import { QualityAssuranceTopicsPageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-topics-page/quality-assurance-topics-page-resolver.service'; -import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component'; -import { - PUBLICATION_CLAIMS_PATH, - QUALITY_ASSURANCE_EDIT_PATH, -} from './admin-notifications-routing-paths'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { - canActivate: [ AuthenticatedGuard ], - path: `${PUBLICATION_CLAIMS_PATH}`, - component: AdminNotificationsPublicationClaimPageComponent, - pathMatch: 'full', - resolve: { - breadcrumb: I18nBreadcrumbResolver, - suggestionTargetParams: AdminNotificationsPublicationClaimPageResolver, - }, - data: { - title: 'admin.notifications.publicationclaim.page.title', - breadcrumbKey: 'admin.notifications.publicationclaim', - showBreadcrumbsFluid: false, - }, - }, - { - canActivate: [ AuthenticatedGuard ], - path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId`, - component: QualityAssuranceTopicsPageComponent, - pathMatch: 'full', - resolve: { - breadcrumb: QualityAssuranceBreadcrumbResolver, - openaireQualityAssuranceTopicsParams: QualityAssuranceTopicsPageResolver, - }, - data: { - title: 'admin.quality-assurance.page.title', - breadcrumbKey: 'admin.quality-assurance', - showBreadcrumbsFluid: false, - }, - }, - { - canActivate: [ AuthenticatedGuard ], - path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/target/:targetId`, - component: QualityAssuranceTopicsPageComponent, - pathMatch: 'full', - resolve: { - breadcrumb: I18nBreadcrumbResolver, - openaireQualityAssuranceTopicsParams: QualityAssuranceTopicsPageResolver, - }, - data: { - title: 'admin.quality-assurance.page.title', - breadcrumbKey: 'admin.quality-assurance', - showBreadcrumbsFluid: false, - }, - }, - { - canActivate: [ SiteAdministratorGuard ], - path: `${QUALITY_ASSURANCE_EDIT_PATH}`, - component: QualityAssuranceSourcePageComponent, - pathMatch: 'full', - resolve: { - breadcrumb: I18nBreadcrumbResolver, - openaireQualityAssuranceSourceParams: QualityAssuranceSourcePageResolver, - sourceData: SourceDataResolver, - }, - data: { - title: 'admin.notifications.source.breadcrumbs', - breadcrumbKey: 'admin.notifications.source', - showBreadcrumbsFluid: false, - }, - }, - { - canActivate: [ AuthenticatedGuard ], - path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/:topicId`, - component: QualityAssuranceEventsPageComponent, - pathMatch: 'full', - resolve: { - breadcrumb: QualityAssuranceBreadcrumbResolver, - openaireQualityAssuranceEventsParams: QualityAssuranceEventsPageResolver, - }, - data: { - title: 'admin.notifications.event.page.title', - breadcrumbKey: 'admin.notifications.event', - showBreadcrumbsFluid: false, - }, - }, - ]), - ], - providers: [ - I18nBreadcrumbResolver, - I18nBreadcrumbsService, - AdminNotificationsPublicationClaimPageResolver, - SourceDataResolver, - QualityAssuranceSourcePageResolver, - QualityAssuranceTopicsPageResolver, - QualityAssuranceEventsPageResolver, - QualityAssuranceSourcePageResolver, - QualityAssuranceBreadcrumbResolver, - QualityAssuranceBreadcrumbService, - ], -}) -/** - * Routing module for the Notifications section of the admin sidebar - */ -export class AdminNotificationsRoutingModule { - -} diff --git a/src/app/admin/admin-notifications/admin-notifications.module.ts b/src/app/admin/admin-notifications/admin-notifications.module.ts deleted file mode 100644 index e2149cda7ac..00000000000 --- a/src/app/admin/admin-notifications/admin-notifications.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; - -import { CoreModule } from '../../core/core.module'; -import { NotificationsModule } from '../../notifications/notifications.module'; -import { SharedModule } from '../../shared/shared.module'; -import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component'; -import { AdminNotificationsRoutingModule } from './admin-notifications-routing.module'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - CoreModule.forRoot(), - AdminNotificationsRoutingModule, - NotificationsModule, - ], - declarations: [ - AdminNotificationsPublicationClaimPageComponent, - ], - entryComponents: [], -}) -/** - * This module handles all components related to the notifications pages - */ -export class AdminNotificationsModule { - -} diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routes.ts b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routes.ts new file mode 100644 index 00000000000..c193148cc4d --- /dev/null +++ b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routes.ts @@ -0,0 +1,51 @@ +import { + mapToCanActivate, + Route, +} from '@angular/router'; + +import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { notifyInfoGuard } from '../../core/coar-notify/notify-info/notify-info.guard'; +import { SiteAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { AdminNotifyDashboardComponent } from './admin-notify-dashboard.component'; +import { AdminNotifyIncomingComponent } from './admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component'; +import { AdminNotifyOutgoingComponent } from './admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component'; + +export const ROUTES: Route[] = [ + { + canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard], + path: '', + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + component: AdminNotifyDashboardComponent, + pathMatch: 'full', + data: { + title: 'admin.notify.dashboard.page.title', + breadcrumbKey: 'admin.notify.dashboard', + }, + }, + { + path: 'inbound', + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + component: AdminNotifyIncomingComponent, + canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard], + data: { + title: 'admin.notify.dashboard.page.title', + breadcrumbKey: 'admin.notify.dashboard', + }, + }, + { + path: 'outbound', + resolve: { + breadcrumb: i18nBreadcrumbResolver, + }, + component: AdminNotifyOutgoingComponent, + canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard], + data: { + title: 'admin.notify.dashboard.page.title', + breadcrumbKey: 'admin.notify.dashboard', + }, + }, +]; diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routing.module.ts b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routing.module.ts deleted file mode 100644 index dc9312ec280..00000000000 --- a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routing.module.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { NotifyInfoGuard } from '../../core/coar-notify/notify-info/notify-info.guard'; -import { SiteAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; -import { AdminNotifyDashboardComponent } from './admin-notify-dashboard.component'; -import { AdminNotifyIncomingComponent } from './admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component'; -import { AdminNotifyOutgoingComponent } from './admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { - canActivate: [SiteAdministratorGuard, NotifyInfoGuard], - path: '', - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - component: AdminNotifyDashboardComponent, - pathMatch: 'full', - data: { - title: 'admin.notify.dashboard.page.title', - breadcrumbKey: 'admin.notify.dashboard', - }, - }, - { - path: 'inbound', - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - component: AdminNotifyIncomingComponent, - canActivate: [SiteAdministratorGuard, NotifyInfoGuard], - data: { - title: 'admin.notify.dashboard.page.title', - breadcrumbKey: 'admin.notify.dashboard', - }, - }, - { - path: 'outbound', - resolve: { - breadcrumb: I18nBreadcrumbResolver, - }, - component: AdminNotifyOutgoingComponent, - canActivate: [SiteAdministratorGuard, NotifyInfoGuard], - data: { - title: 'admin.notify.dashboard.page.title', - breadcrumbKey: 'admin.notify.dashboard', - }, - }, - ]), - ], -}) -/** - * Routing module for the Notifications section of the admin sidebar - */ -export class AdminNotifyDashboardRoutingModule { - -} diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.spec.ts b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.spec.ts index 26293133631..a8bc2eea8e7 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.spec.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.spec.ts @@ -1,14 +1,18 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { buildPaginatedList } from '../../core/data/paginated-list.model'; import { SearchService } from '../../core/shared/search/search.service'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { AdminNotifyDashboardComponent } from './admin-notify-dashboard.component'; +import { AdminNotifyMetricsComponent } from './admin-notify-metrics/admin-notify-metrics.component'; import { AdminNotifyMessage } from './models/admin-notify-message.model'; import { AdminNotifySearchResult } from './models/admin-notify-message-search-result.model'; @@ -39,9 +43,16 @@ describe('AdminNotifyDashboardComponent', () => { results = buildPaginatedList(undefined, [searchResult1, searchResult2, searchResult3]); await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), NgbNavModule], - declarations: [ AdminNotifyDashboardComponent ], - providers: [{ provide: SearchService, useValue: { search: () => createSuccessfulRemoteDataObject$(results) } }], + imports: [TranslateModule.forRoot(), NgbNavModule, AdminNotifyDashboardComponent], + providers: [ + { provide: SearchService, useValue: { search: () => createSuccessfulRemoteDataObject$(results) } }, + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).overrideComponent(AdminNotifyDashboardComponent, { + remove: { + imports: [AdminNotifyMetricsComponent], + }, }) .compileComponents(); diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.ts index 40100d8684c..69ca118fffc 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.ts @@ -1,7 +1,13 @@ +import { + AsyncPipe, + NgIf, +} from '@angular/common'; import { Component, OnInit, } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { forkJoin, Observable, @@ -13,10 +19,11 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { SearchService } from '../../core/shared/search/search.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; -import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component'; +import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-configuration.service'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; import { SearchObjects } from '../../shared/search/models/search-objects.model'; +import { AdminNotifyMetricsComponent } from './admin-notify-metrics/admin-notify-metrics.component'; import { AdminNotifyMetricsBox, AdminNotifyMetricsRow, @@ -31,6 +38,14 @@ import { useClass: SearchConfigurationService, }, ], + standalone: true, + imports: [ + AdminNotifyMetricsComponent, + RouterLink, + NgIf, + TranslateModule, + AsyncPipe, + ], }) /** diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.module.ts b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.module.ts deleted file mode 100644 index d840197ea79..00000000000 --- a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.module.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - CommonModule, - DatePipe, -} from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { SearchPageModule } from '../../search-page/search-page.module'; -import { SearchModule } from '../../shared/search/search.module'; -import { SharedModule } from '../../shared/shared.module'; -import { AdminNotifyDashboardComponent } from './admin-notify-dashboard.component'; -import { AdminNotifyDashboardRoutingModule } from './admin-notify-dashboard-routing.module'; -import { AdminNotifyDetailModalComponent } from './admin-notify-detail-modal/admin-notify-detail-modal.component'; -import { AdminNotifyIncomingComponent } from './admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component'; -import { AdminNotifyLogsResultComponent } from './admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component'; -import { AdminNotifyOutgoingComponent } from './admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component'; -import { AdminNotifyMetricsComponent } from './admin-notify-metrics/admin-notify-metrics.component'; -import { AdminNotifySearchResultComponent } from './admin-notify-search-result/admin-notify-search-result.component'; -import { AdminNotifyMessagesService } from './services/admin-notify-messages.service'; - -const ENTRY_COMPONENTS = [ - AdminNotifySearchResultComponent, -]; -@NgModule({ - imports: [ - CommonModule, - RouterModule, - SharedModule, - AdminNotifyDashboardRoutingModule, - SearchModule, - SearchPageModule, - ], - providers: [ - AdminNotifyMessagesService, - DatePipe, - ], - declarations: [ - ...ENTRY_COMPONENTS, - AdminNotifyDashboardComponent, - AdminNotifyMetricsComponent, - AdminNotifyIncomingComponent, - AdminNotifyOutgoingComponent, - AdminNotifyDetailModalComponent, - AdminNotifySearchResultComponent, - AdminNotifyLogsResultComponent, - ], -}) -export class AdminNotifyDashboardModule { - static withEntryComponents() { - return { - ngModule: AdminNotifyDashboardModule, - providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })), - }; - } -} diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.spec.ts b/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.spec.ts index 7fddf170ff0..3c272dadab0 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.spec.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.spec.ts @@ -15,8 +15,7 @@ describe('AdminNotifyDetailModalComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [ AdminNotifyDetailModalComponent ], + imports: [TranslateModule.forRoot(), AdminNotifyDetailModalComponent], providers: [{ provide: NgbActiveModal, useValue: modalStub }], }) .compileComponents(); diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.ts index 3962d9cb50e..a2013356ab9 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.ts @@ -1,3 +1,7 @@ +import { + NgForOf, + NgIf, +} from '@angular/common'; import { Component, EventEmitter, @@ -5,7 +9,10 @@ import { Output, } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { fadeIn } from '../../../shared/animations/fade'; import { MissingTranslationHelper } from '../../../shared/translate/missing-translation.helper'; @@ -17,6 +24,12 @@ import { AdminNotifyMessage } from '../models/admin-notify-message.model'; animations: [ fadeIn, ], + standalone: true, + imports: [ + NgForOf, + TranslateModule, + NgIf, + ], }) /** * Component for detailed view of LDN messages displayed in search result in AdminNotifyDashboardComponent diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component.spec.ts b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component.spec.ts index 70771bac544..054dac3218a 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component.spec.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component.spec.ts @@ -6,15 +6,17 @@ import { ActivatedRoute } from '@angular/router'; import { provideMockStore } from '@ngrx/store/testing'; import { TranslateModule } from '@ngx-translate/core'; +import { APP_DATA_SERVICES_MAP } from '../../../../../config/app-config.interface'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { RequestService } from '../../../../core/data/request.service'; import { RouteService } from '../../../../core/services/route.service'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; -import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-configuration.service'; import { MockActivatedRoute } from '../../../../shared/mocks/active-router.mock'; import { getMockRemoteDataBuildService } from '../../../../shared/mocks/remote-data-build.service.mock'; import { routeServiceStub } from '../../../../shared/testing/route-service.stub'; +import { AdminNotifyLogsResultComponent } from '../admin-notify-logs-result/admin-notify-logs-result.component'; import { AdminNotifyIncomingComponent } from './admin-notify-incoming.component'; describe('AdminNotifyIncomingComponent', () => { @@ -36,8 +38,7 @@ describe('AdminNotifyIncomingComponent', () => { 'send': '', }); await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [ AdminNotifyIncomingComponent ], + imports: [TranslateModule.forRoot(), AdminNotifyIncomingComponent], providers: [ { provide: SEARCH_CONFIG_SERVICE, useValue: SearchConfigurationService }, { provide: RouteService, useValue: routeServiceStub }, @@ -45,8 +46,11 @@ describe('AdminNotifyIncomingComponent', () => { { provide: HALEndpointService, useValue: halService }, { provide: RequestService, useValue: requestService }, { provide: RemoteDataBuildService, useValue: rdbService }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, provideMockStore({}), ], + }).overrideComponent(AdminNotifyIncomingComponent, { + remove: { imports: [AdminNotifyLogsResultComponent] }, }) .compileComponents(); diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component.ts index fc0f410ad57..aaaec8437d0 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component.ts @@ -2,9 +2,12 @@ import { Component, Inject, } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; -import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-configuration.service'; +import { AdminNotifyLogsResultComponent } from '../admin-notify-logs-result/admin-notify-logs-result.component'; @Component({ selector: 'ds-admin-notify-incoming', @@ -15,6 +18,12 @@ import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page useClass: SearchConfigurationService, }, ], + standalone: true, + imports: [ + RouterLink, + AdminNotifyLogsResultComponent, + TranslateModule, + ], }) export class AdminNotifyIncomingComponent { constructor(@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) { diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.spec.ts b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.spec.ts index 16adbd17f3c..985e58bfcba 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.spec.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.spec.ts @@ -9,12 +9,15 @@ import { import { provideMockStore } from '@ngrx/store/testing'; import { TranslateModule } from '@ngx-translate/core'; +import { APP_DATA_SERVICES_MAP } from '../../../../../config/app-config.interface'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { RequestService } from '../../../../core/data/request.service'; import { RouteService } from '../../../../core/services/route.service'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { MockActivatedRoute } from '../../../../shared/mocks/active-router.mock'; +import { SearchLabelsComponent } from '../../../../shared/search/search-labels/search-labels.component'; +import { ThemedSearchComponent } from '../../../../shared/search/themed-search.component'; import { routeServiceStub } from '../../../../shared/testing/route-service.stub'; import { RouterStub } from '../../../../shared/testing/router.stub'; import { AdminNotifyLogsResultComponent } from './admin-notify-logs-result.component'; @@ -29,8 +32,7 @@ describe('AdminNotifyLogsResultComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [ AdminNotifyLogsResultComponent ], + imports: [TranslateModule.forRoot(), AdminNotifyLogsResultComponent], providers: [ { provide: RouteService, useValue: routeServiceStub }, { provide: Router, useValue: new RouterStub() }, @@ -38,10 +40,19 @@ describe('AdminNotifyLogsResultComponent', () => { { provide: HALEndpointService, useValue: halService }, { provide: ObjectCacheService, useValue: objectCache }, { provide: RequestService, useValue: requestService }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, { provide: RemoteDataBuildService, useValue: rdbService }, provideMockStore({}), ], }) + .overrideComponent(AdminNotifyLogsResultComponent, { + remove: { + imports: [ + SearchLabelsComponent, + ThemedSearchComponent, + ], + }, + }) .compileComponents(); fixture = TestBed.createComponent(AdminNotifyLogsResultComponent); diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.ts index 626cd2da1f4..4cb6696fe25 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.ts @@ -1,3 +1,7 @@ +import { + AsyncPipe, + NgIf, +} from '@angular/common'; import { ChangeDetectorRef, Component, @@ -10,13 +14,16 @@ import { ActivatedRouteSnapshot, Router, } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { Context } from '../../../../core/shared/context.model'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; import { ViewMode } from '../../../../core/shared/view-mode.model'; -import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-configuration.service'; +import { SearchLabelsComponent } from '../../../../shared/search/search-labels/search-labels.component'; +import { ThemedSearchComponent } from '../../../../shared/search/themed-search.component'; @Component({ selector: 'ds-admin-notify-logs-result', @@ -27,6 +34,14 @@ import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page useClass: SearchConfigurationService, }, ], + standalone: true, + imports: [ + SearchLabelsComponent, + ThemedSearchComponent, + AsyncPipe, + TranslateModule, + NgIf, + ], }) /** diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component.spec.ts b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component.spec.ts index 73773736ff7..0cdb87ac17b 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component.spec.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component.spec.ts @@ -6,15 +6,17 @@ import { ActivatedRoute } from '@angular/router'; import { provideMockStore } from '@ngrx/store/testing'; import { TranslateModule } from '@ngx-translate/core'; +import { APP_DATA_SERVICES_MAP } from '../../../../../config/app-config.interface'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { RequestService } from '../../../../core/data/request.service'; import { RouteService } from '../../../../core/services/route.service'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; -import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-configuration.service'; import { MockActivatedRoute } from '../../../../shared/mocks/active-router.mock'; import { getMockRemoteDataBuildService } from '../../../../shared/mocks/remote-data-build.service.mock'; import { routeServiceStub } from '../../../../shared/testing/route-service.stub'; +import { AdminNotifyLogsResultComponent } from '../admin-notify-logs-result/admin-notify-logs-result.component'; import { AdminNotifyOutgoingComponent } from './admin-notify-outgoing.component'; describe('AdminNotifyOutgoingComponent', () => { @@ -36,9 +38,9 @@ describe('AdminNotifyOutgoingComponent', () => { }); await TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [ AdminNotifyOutgoingComponent ], providers: [ { provide: SEARCH_CONFIG_SERVICE, useValue: SearchConfigurationService }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, { provide: RouteService, useValue: routeServiceStub }, { provide: ActivatedRoute, useValue: new MockActivatedRoute() }, { provide: HALEndpointService, useValue: halService }, @@ -47,6 +49,9 @@ describe('AdminNotifyOutgoingComponent', () => { provideMockStore({}), ], }) + .overrideComponent(AdminNotifyOutgoingComponent, { + remove: { imports: [AdminNotifyLogsResultComponent] }, + }) .compileComponents(); fixture = TestBed.createComponent(AdminNotifyOutgoingComponent); diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component.ts index 26d6d345bea..79a30b7961b 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component.ts @@ -2,9 +2,12 @@ import { Component, Inject, } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; -import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-configuration.service'; +import { AdminNotifyLogsResultComponent } from '../admin-notify-logs-result/admin-notify-logs-result.component'; @Component({ selector: 'ds-admin-notify-outgoing', @@ -15,6 +18,12 @@ import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page useClass: SearchConfigurationService, }, ], + standalone: true, + imports: [ + RouterLink, + AdminNotifyLogsResultComponent, + TranslateModule, + ], }) export class AdminNotifyOutgoingComponent { constructor(@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) { diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.spec.ts b/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.spec.ts index 3411a6134f9..0318fe58c5b 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.spec.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.spec.ts @@ -21,8 +21,7 @@ describe('AdminNotifyMetricsComponent', () => { await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [ AdminNotifyMetricsComponent ], + imports: [TranslateModule.forRoot(), AdminNotifyMetricsComponent], providers: [{ provide: Router, useValue: router }], }) .compileComponents(); diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.ts index 6d08f21415a..727b081d89f 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.ts @@ -1,15 +1,24 @@ +import { NgForOf } from '@angular/common'; import { Component, Input, } from '@angular/core'; import { Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { ViewMode } from '../../../core/shared/view-mode.model'; +import { NotificationBoxComponent } from '../../../shared/notification-box/notification-box.component'; import { AdminNotifyMetricsRow } from './admin-notify-metrics.model'; @Component({ selector: 'ds-admin-notify-metrics', templateUrl: './admin-notify-metrics.component.html', + standalone: true, + imports: [ + NotificationBoxComponent, + TranslateModule, + NgForOf, + ], }) /** * Component used to display the number of notification for each configured box in the notifyMetrics section diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.spec.ts b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.spec.ts index 1a6411cb603..3bab1ab5bbd 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.spec.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.spec.ts @@ -13,6 +13,7 @@ import { of, } from 'rxjs'; +import { APP_DATA_SERVICES_MAP } from '../../../../config/app-config.interface'; import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { RequestService } from '../../../core/data/request.service'; @@ -20,9 +21,11 @@ import { RouteService } from '../../../core/services/route.service'; import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; -import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service'; import { routeServiceStub } from '../../../shared/testing/route-service.stub'; import { RouterStub } from '../../../shared/testing/router.stub'; +import { TruncatableComponent } from '../../../shared/truncatable/truncatable.component'; +import { TruncatablePartComponent } from '../../../shared/truncatable/truncatable-part/truncatable-part.component'; import { AdminNotifyDetailModalComponent } from '../admin-notify-detail-modal/admin-notify-detail-modal.component'; import { AdminNotifyMessage } from '../models/admin-notify-message.model'; import { AdminNotifyMessagesService } from '../services/admin-notify-messages.service'; @@ -128,8 +131,7 @@ describe('AdminNotifySearchResultComponent', () => { await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [ AdminNotifySearchResultComponent, AdminNotifyDetailModalComponent ], + imports: [TranslateModule.forRoot(), AdminNotifySearchResultComponent, AdminNotifyDetailModalComponent], providers: [ { provide: AdminNotifyMessagesService, useValue: adminNotifyMessageService }, { provide: RouteService, useValue: routeServiceStub }, @@ -139,10 +141,19 @@ describe('AdminNotifySearchResultComponent', () => { { provide: RequestService, useValue: requestService }, { provide: RemoteDataBuildService, useValue: rdbService }, { provide: SEARCH_CONFIG_SERVICE, useValue: searchConfigService }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, DatePipe, ], schemas: [NO_ERRORS_SCHEMA], }) + .overrideComponent(AdminNotifySearchResultComponent, { + remove: { + imports: [ + TruncatableComponent, + TruncatablePartComponent, + ], + }, + }) .compileComponents(); fixture = TestBed.createComponent(AdminNotifySearchResultComponent); diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts index a9a329d0b5e..f1c8d9ead60 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts @@ -1,44 +1,59 @@ -import { DatePipe } from '@angular/common'; +import { + AsyncPipe, + DatePipe, + NgForOf, + NgIf, +} from '@angular/common'; import { Component, Inject, OnDestroy, OnInit, } from '@angular/core'; +import { RouterLink } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; import { BehaviorSubject, Subscription, } from 'rxjs'; import { PaginatedList } from '../../../core/data/paginated-list.model'; -import { Context } from '../../../core/shared/context.model'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; -import { ViewMode } from '../../../core/shared/view-mode.model'; -import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; -import { tabulatableObjectsComponent } from '../../../shared/object-collection/shared/tabulatable-objects/tabulatable-objects.decorator'; +import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service'; import { TabulatableResultListElementsComponent } from '../../../shared/object-list/search-result-list-element/tabulatable-search-result/tabulatable-result-list-elements.component'; +import { TruncatableComponent } from '../../../shared/truncatable/truncatable.component'; +import { TruncatablePartComponent } from '../../../shared/truncatable/truncatable-part/truncatable-part.component'; import { AdminNotifyDetailModalComponent } from '../admin-notify-detail-modal/admin-notify-detail-modal.component'; import { AdminNotifyMessage } from '../models/admin-notify-message.model'; import { AdminNotifySearchResult } from '../models/admin-notify-message-search-result.model'; import { AdminNotifyMessagesService } from '../services/admin-notify-messages.service'; -@tabulatableObjectsComponent(PaginatedList, ViewMode.Table, Context.CoarNotify) @Component({ selector: 'ds-admin-notify-search-result', templateUrl: './admin-notify-search-result.component.html', providers: [ + DatePipe, { provide: SEARCH_CONFIG_SERVICE, useClass: SearchConfigurationService, }, ], + standalone: true, + imports: [ + TranslateModule, + NgForOf, + NgIf, + DatePipe, + AsyncPipe, + TruncatableComponent, + TruncatablePartComponent, + RouterLink, + ], }) /** * Component for visualization in table format of the search results related to the AdminNotifyDashboardComponent */ - - export class AdminNotifySearchResultComponent extends TabulatableResultListElementsComponent, AdminNotifySearchResult> implements OnInit, OnDestroy{ public messagesSubject$: BehaviorSubject = new BehaviorSubject([]); public reprocessStatus = 'QUEUE_STATUS_QUEUED_FOR_RETRY'; diff --git a/src/app/admin/admin-notify-dashboard/models/admin-notify-message-search-result.model.ts b/src/app/admin/admin-notify-dashboard/models/admin-notify-message-search-result.model.ts index 51151189936..c4df75ef3ee 100644 --- a/src/app/admin/admin-notify-dashboard/models/admin-notify-message-search-result.model.ts +++ b/src/app/admin/admin-notify-dashboard/models/admin-notify-message-search-result.model.ts @@ -1,7 +1,5 @@ import { SearchResult } from '../../../shared/search/models/search-result.model'; -import { searchResultFor } from '../../../shared/search/search-result-element-decorator'; import { AdminNotifyMessage } from './admin-notify-message.model'; -@searchResultFor(AdminNotifyMessage) export class AdminNotifySearchResult extends SearchResult { } diff --git a/src/app/admin/admin-notify-dashboard/services/admin-notify-messages.service.ts b/src/app/admin/admin-notify-dashboard/services/admin-notify-messages.service.ts index 04f26c29afc..2211facfc87 100644 --- a/src/app/admin/admin-notify-dashboard/services/admin-notify-messages.service.ts +++ b/src/app/admin/admin-notify-dashboard/services/admin-notify-messages.service.ts @@ -15,7 +15,6 @@ import { import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; -import { dataService } from '../../../core/data/base/data-service.decorator'; import { IdentifiableDataService } from '../../../core/data/base/identifiable-data.service'; import { ItemDataService } from '../../../core/data/item-data.service'; import { PostRequest } from '../../../core/data/request.models'; @@ -29,17 +28,15 @@ import { import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { LdnServicesService } from '../../admin-ldn-services/ldn-services-data/ldn-services-data.service'; import { AdminNotifyMessage } from '../models/admin-notify-message.model'; -import { ADMIN_NOTIFY_MESSAGE } from '../models/admin-notify-message.resource-type'; /** - * Injectable service responsible for fetching/sending data from/to the REST API on the messages endpoint. + * Injectable service responsible for fetching/sending data from/to the REST API on the messages' endpoint. * * @export * @class AdminNotifyMessagesService * @extends {IdentifiableDataService} */ -@Injectable() -@dataService(ADMIN_NOTIFY_MESSAGE) +@Injectable({ providedIn: 'root' }) export class AdminNotifyMessagesService extends IdentifiableDataService { protected reprocessEndpoint = 'enqueueretry'; diff --git a/src/app/admin/admin-registries/admin-registries-routes.ts b/src/app/admin/admin-registries/admin-registries-routes.ts new file mode 100644 index 00000000000..06aaa934ab6 --- /dev/null +++ b/src/app/admin/admin-registries/admin-registries-routes.ts @@ -0,0 +1,33 @@ +import { Route } from '@angular/router'; + +import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { BITSTREAMFORMATS_MODULE_PATH } from './admin-registries-routing-paths'; +import { MetadataRegistryComponent } from './metadata-registry/metadata-registry.component'; +import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component'; + +export const ROUTES: Route[] = [ + { + path: 'metadata', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + data: { title: 'admin.registries.metadata.title', breadcrumbKey: 'admin.registries.metadata' }, + children: [ + { + path: '', + component: MetadataRegistryComponent, + }, + { + path: ':schemaName', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + component: MetadataSchemaComponent, + data: { title: 'admin.registries.schema.title', breadcrumbKey: 'admin.registries.schema' }, + }, + ], + }, + { + path: BITSTREAMFORMATS_MODULE_PATH, + resolve: { breadcrumb: i18nBreadcrumbResolver }, + loadChildren: () => import('./bitstream-formats/bitstream-formats-routes') + .then((m) => m.ROUTES), + data: { title: 'admin.registries.bitstream-formats.title', breadcrumbKey: 'admin.registries.bitstream-formats' }, + }, +]; diff --git a/src/app/admin/admin-registries/admin-registries-routing.module.ts b/src/app/admin/admin-registries/admin-registries-routing.module.ts deleted file mode 100644 index 3134d3285eb..00000000000 --- a/src/app/admin/admin-registries/admin-registries-routing.module.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { BITSTREAMFORMATS_MODULE_PATH } from './admin-registries-routing-paths'; -import { MetadataRegistryComponent } from './metadata-registry/metadata-registry.component'; -import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { - path: 'metadata', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - data: { title: 'admin.registries.metadata.title', breadcrumbKey: 'admin.registries.metadata' }, - children: [ - { - path: '', - component: MetadataRegistryComponent, - }, - { - path: ':schemaName', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - component: MetadataSchemaComponent, - data: { title: 'admin.registries.schema.title', breadcrumbKey: 'admin.registries.schema' }, - }, - ], - }, - { - path: BITSTREAMFORMATS_MODULE_PATH, - resolve: { breadcrumb: I18nBreadcrumbResolver }, - loadChildren: () => import('./bitstream-formats/bitstream-formats.module') - .then((m) => m.BitstreamFormatsModule), - data: { title: 'admin.registries.bitstream-formats.title', breadcrumbKey: 'admin.registries.bitstream-formats' }, - }, - ]), - ], -}) -export class AdminRegistriesRoutingModule { - -} diff --git a/src/app/admin/admin-registries/admin-registries.module.ts b/src/app/admin/admin-registries/admin-registries.module.ts deleted file mode 100644 index 42432a90eda..00000000000 --- a/src/app/admin/admin-registries/admin-registries.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { FormModule } from '../../shared/form/form.module'; -import { SharedModule } from '../../shared/shared.module'; -import { AdminRegistriesRoutingModule } from './admin-registries-routing.module'; -import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module'; -import { MetadataRegistryComponent } from './metadata-registry/metadata-registry.component'; -import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component'; -import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component'; -import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule, - BitstreamFormatsModule, - AdminRegistriesRoutingModule, - FormModule, - ], - declarations: [ - MetadataRegistryComponent, - MetadataSchemaComponent, - MetadataSchemaFormComponent, - MetadataFieldFormComponent, - ], -}) -export class AdminRegistriesModule { - -} diff --git a/src/app/admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts b/src/app/admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts index 72d27951704..bdab873717a 100644 --- a/src/app/admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts +++ b/src/app/admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts @@ -14,6 +14,10 @@ import { of as observableOf } from 'rxjs'; import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level'; +import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; +import { FormService } from '../../../../shared/form/form.service'; +import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; +import { getMockFormService } from '../../../../shared/mocks/form-service.mock'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { createFailedRemoteDataObject$, @@ -21,6 +25,7 @@ import { } from '../../../../shared/remote-data.utils'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { RouterStub } from '../../../../shared/testing/router.stub'; +import { FormatFormComponent } from '../format-form/format-form.component'; import { AddBitstreamFormatComponent } from './add-bitstream-format.component'; describe('AddBitstreamFormatComponent', () => { @@ -50,15 +55,22 @@ describe('AddBitstreamFormatComponent', () => { }); TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [AddBitstreamFormatComponent], + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, AddBitstreamFormatComponent], providers: [ { provide: Router, useValue: router }, { provide: NotificationsService, useValue: notificationService }, { provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService }, + { provide: FormService, useValue: getMockFormService() }, + { provide: FormBuilderService, useValue: getMockFormBuilderService() }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(AddBitstreamFormatComponent, { + remove: { + imports: [FormatFormComponent], + }, + }) + .compileComponents(); }; const initBeforeEach = () => { @@ -90,15 +102,22 @@ describe('AddBitstreamFormatComponent', () => { }); TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [AddBitstreamFormatComponent], + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, AddBitstreamFormatComponent], providers: [ { provide: Router, useValue: router }, { provide: NotificationsService, useValue: notificationService }, { provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService }, + { provide: FormService, useValue: getMockFormService() }, + { provide: FormBuilderService, useValue: getMockFormBuilderService() }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(AddBitstreamFormatComponent, { + remove: { + imports: [FormatFormComponent], + }, + }) + .compileComponents(); })); beforeEach(initBeforeEach); it('should send the updated form to the service, show a notification and navigate to ', () => { diff --git a/src/app/admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts b/src/app/admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts index 4dc43480c04..a1ba387cfbd 100644 --- a/src/app/admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts +++ b/src/app/admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts @@ -1,6 +1,9 @@ import { Component } from '@angular/core'; import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; import { RemoteData } from '../../../../core/data/remote-data'; @@ -8,6 +11,7 @@ import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model' import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths'; +import { FormatFormComponent } from '../format-form/format-form.component'; /** * This component renders the page to create a new bitstream format. @@ -15,6 +19,11 @@ import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-p @Component({ selector: 'ds-add-bitstream-format', templateUrl: './add-bitstream-format.component.html', + imports: [ + FormatFormComponent, + TranslateModule, + ], + standalone: true, }) export class AddBitstreamFormatComponent { diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats-routes.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats-routes.ts new file mode 100644 index 00000000000..5c85d194a50 --- /dev/null +++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats-routes.ts @@ -0,0 +1,37 @@ +import { Route } from '@angular/router'; + +import { i18nBreadcrumbResolver } from '../../../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component'; +import { BitstreamFormatsComponent } from './bitstream-formats.component'; +import { bitstreamFormatsResolver } from './bitstream-formats.resolver'; +import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component'; + +const BITSTREAMFORMAT_EDIT_PATH = ':id/edit'; +const BITSTREAMFORMAT_ADD_PATH = 'add'; + +const providers = []; + +export const ROUTES: Route[] = [ + { + path: '', + providers, + component: BitstreamFormatsComponent, + }, + { + path: BITSTREAMFORMAT_ADD_PATH, + resolve: { breadcrumb: i18nBreadcrumbResolver }, + providers, + component: AddBitstreamFormatComponent, + data: { breadcrumbKey: 'admin.registries.bitstream-formats.create' }, + }, + { + path: BITSTREAMFORMAT_EDIT_PATH, + providers, + component: EditBitstreamFormatComponent, + resolve: { + bitstreamFormat: bitstreamFormatsResolver, + breadcrumb: i18nBreadcrumbResolver, + }, + data: { breadcrumbKey: 'admin.registries.bitstream-formats.edit' }, + }, +]; diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts deleted file mode 100644 index e4a2a36f260..00000000000 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { I18nBreadcrumbResolver } from '../../../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component'; -import { BitstreamFormatsComponent } from './bitstream-formats.component'; -import { BitstreamFormatsResolver } from './bitstream-formats.resolver'; -import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component'; - -const BITSTREAMFORMAT_EDIT_PATH = ':id/edit'; -const BITSTREAMFORMAT_ADD_PATH = 'add'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { - path: '', - component: BitstreamFormatsComponent, - }, - { - path: BITSTREAMFORMAT_ADD_PATH, - resolve: { breadcrumb: I18nBreadcrumbResolver }, - component: AddBitstreamFormatComponent, - data: { breadcrumbKey: 'admin.registries.bitstream-formats.create' }, - }, - { - path: BITSTREAMFORMAT_EDIT_PATH, - component: EditBitstreamFormatComponent, - resolve: { - bitstreamFormat: BitstreamFormatsResolver, - breadcrumb: I18nBreadcrumbResolver, - }, - data: { breadcrumbKey: 'admin.registries.bitstream-formats.edit' }, - }, - ]), - ], - providers: [ - BitstreamFormatsResolver, - ], -}) -export class BitstreamFormatsRoutingModule { - -} diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts index b63a6213eba..9ee767cda05 100644 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts +++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts @@ -1,4 +1,8 @@ import { CommonModule } from '@angular/common'; +import { + Component, + NO_ERRORS_SCHEMA, +} from '@angular/core'; import { ComponentFixture, TestBed, @@ -7,19 +11,27 @@ import { import { By } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { provideMockStore } from '@ngrx/store/testing'; import { TranslateModule } from '@ngx-translate/core'; import { cold, getTestScheduler, hot, } from 'jasmine-marbles'; -import { of as observableOf } from 'rxjs'; +import { + of as observableOf, + of, +} from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; +import { APP_DATA_SERVICES_MAP } from '../../../../config/app-config.interface'; import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; +import { GroupDataService } from '../../../core/eperson/group-data.service'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level'; +import { XSRFService } from '../../../core/xsrf/xsrf.service'; import { HostWindowService } from '../../../shared/host-window.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { PaginationComponent } from '../../../shared/pagination/pagination.component'; @@ -108,17 +120,33 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared'), }); + const groupDataService = jasmine.createSpyObj('groupsDataService', { + findListByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), + getGroupRegistryRouterLink: '', + getUUIDFromString: '', + }); + + + const configurationDataService = jasmine.createSpyObj('ConfigurationDataService', { + findByPropertyName: of({ payload: { value: 'test' } }), + }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], providers: [ + provideMockStore(), + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: NotificationsService, useValue: notificationsServiceStub }, { provide: PaginationService, useValue: paginationService }, + { provide: GroupDataService, useValue: groupDataService }, + { provide: ConfigurationDataService, useValue: configurationDataService }, + { provide: XSRFService, useValue: {} }, ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }; @@ -236,18 +264,39 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared'), }); + const groupDataService = jasmine.createSpyObj('groupsDataService', { + findListByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), + getGroupRegistryRouterLink: '', + getUUIDFromString: '', + }); + + const configurationDataService = jasmine.createSpyObj('ConfigurationDataService', { + findByPropertyName: of({ payload: { value: 'test' } }), + }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], + imports: [ + CommonModule, + RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, + BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], providers: [ + provideMockStore(), { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: NotificationsService, useValue: notificationsServiceStub }, { provide: PaginationService, useValue: paginationService }, + { provide: GroupDataService, useValue: groupDataService }, + { provide: ConfigurationDataService, useValue: configurationDataService }, ], - }).compileComponents(); + schemas: [NO_ERRORS_SCHEMA], + }) + .overrideComponent(BitstreamFormatsComponent, { + remove: { imports: [PaginationComponent] }, + add: { imports: [TestPaginationComponent] }, + }) + .compileComponents(); }, )); @@ -285,18 +334,37 @@ describe('BitstreamFormatsComponent', () => { clearBitStreamFormatRequests: observableOf('cleared'), }); + const groupDataService = jasmine.createSpyObj('groupsDataService', { + findListByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), + getGroupRegistryRouterLink: '', + getUUIDFromString: '', + }); + + const configurationDataService = jasmine.createSpyObj('ConfigurationDataService', { + findByPropertyName: of({ payload: { value: 'test' } }), + }); + paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], providers: [ + provideMockStore(), { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: NotificationsService, useValue: notificationsServiceStub }, { provide: PaginationService, useValue: paginationService }, + { provide: PaginationService, useValue: paginationService }, + { provide: GroupDataService, useValue: groupDataService }, + { provide: ConfigurationDataService, useValue: configurationDataService }, ], - }).compileComponents(); + schemas: [NO_ERRORS_SCHEMA], + }) + .overrideComponent(BitstreamFormatsComponent, { + remove: { imports: [PaginationComponent] }, + add: { imports: [TestPaginationComponent] }, + }) + .compileComponents(); }, )); @@ -316,3 +384,12 @@ describe('BitstreamFormatsComponent', () => { }); }); }); + +@Component({ + exportAs: 'paginationComponent', + selector: 'ds-pagination', + template: ``, + standalone: true, +}) +export class TestPaginationComponent { +} diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index 522f4667351..72739507be3 100644 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -1,10 +1,21 @@ +import { + AsyncPipe, + NgForOf, + NgIf, +} from '@angular/common'; import { Component, OnDestroy, OnInit, } from '@angular/core'; -import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + Router, + RouterLink, +} from '@angular/router'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { combineLatest as observableCombineLatest, Observable, @@ -26,6 +37,7 @@ import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { NoContent } from '../../../core/shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; /** @@ -34,6 +46,15 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio @Component({ selector: 'ds-bitstream-formats', templateUrl: './bitstream-formats.component.html', + imports: [ + NgIf, + AsyncPipe, + RouterLink, + TranslateModule, + PaginationComponent, + NgForOf, + ], + standalone: true, }) export class BitstreamFormatsComponent implements OnInit, OnDestroy { diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.module.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.module.ts deleted file mode 100644 index 4c8b3284b02..00000000000 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { FormModule } from '../../../shared/form/form.module'; -import { SharedModule } from '../../../shared/shared.module'; -import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component'; -import { BitstreamFormatsComponent } from './bitstream-formats.component'; -import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module'; -import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component'; -import { FormatFormComponent } from './format-form/format-form.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule, - BitstreamFormatsRoutingModule, - FormModule, - ], - declarations: [ - BitstreamFormatsComponent, - EditBitstreamFormatComponent, - AddBitstreamFormatComponent, - FormatFormComponent, - ], -}) -export class BitstreamFormatsModule { - -} diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts index 4ab186cdc62..366f5a682b5 100644 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts +++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@angular/core'; +import { inject } from '@angular/core'; import { ActivatedRouteSnapshot, - Resolve, + ResolveFn, RouterStateSnapshot, } from '@angular/router'; import { Observable } from 'rxjs'; @@ -12,24 +12,20 @@ import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; /** - * This class represents a resolver that requests a specific bitstreamFormat before the route is activated + * Method for resolving an bitstreamFormat based on the parameters in the current route + * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot + * @param {RouterStateSnapshot} state + * @param {BitstreamFormatDataService} bitstreamFormatDataService The BitstreamFormatDataService + * @returns Observable<> Emits the found bitstreamFormat based on the parameters in the current route, + * or an error if something went wrong */ -@Injectable() -export class BitstreamFormatsResolver implements Resolve> { - constructor(private bitstreamFormatDataService: BitstreamFormatDataService) { - } - - /** - * Method for resolving an bitstreamFormat based on the parameters in the current route - * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot - * @param {RouterStateSnapshot} state The current RouterStateSnapshot - * @returns Observable<> Emits the found bitstreamFormat based on the parameters in the current route, - * or an error if something went wrong - */ - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { - return this.bitstreamFormatDataService.findById(route.params.id) - .pipe( - getFirstCompletedRemoteData(), - ); - } -} +export const bitstreamFormatsResolver: ResolveFn> = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, + bitstreamFormatDataService: BitstreamFormatDataService = inject(BitstreamFormatDataService), +): Observable> => { + return bitstreamFormatDataService.findById(route.params.id) + .pipe( + getFirstCompletedRemoteData(), + ); +}; diff --git a/src/app/admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts b/src/app/admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts index 5df6f74c13d..17276ef2ac4 100644 --- a/src/app/admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts +++ b/src/app/admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts @@ -26,6 +26,7 @@ import { } from '../../../../shared/remote-data.utils'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { RouterStub } from '../../../../shared/testing/router.stub'; +import { FormatFormComponent } from '../format-form/format-form.component'; import { EditBitstreamFormatComponent } from './edit-bitstream-format.component'; describe('EditBitstreamFormatComponent', () => { @@ -60,16 +61,22 @@ describe('EditBitstreamFormatComponent', () => { }); TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [EditBitstreamFormatComponent], + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, EditBitstreamFormatComponent], providers: [ { provide: ActivatedRoute, useValue: routeStub }, { provide: Router, useValue: router }, { provide: NotificationsService, useValue: notificationService }, { provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService }, + ], schemas: [CUSTOM_ELEMENTS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(EditBitstreamFormatComponent, { + remove: { + imports: [FormatFormComponent], + }, + }) + .compileComponents(); }; const initBeforeEach = () => { @@ -111,8 +118,7 @@ describe('EditBitstreamFormatComponent', () => { }); TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [EditBitstreamFormatComponent], + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, EditBitstreamFormatComponent], providers: [ { provide: ActivatedRoute, useValue: routeStub }, { provide: Router, useValue: router }, @@ -120,7 +126,13 @@ describe('EditBitstreamFormatComponent', () => { { provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(EditBitstreamFormatComponent, { + remove: { + imports: [FormatFormComponent], + }, + }) + .compileComponents(); })); beforeEach(initBeforeEach); it('should send the updated form to the service, show a notification and navigate to ', () => { diff --git a/src/app/admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts b/src/app/admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts index e7d1d501af7..f932d832775 100644 --- a/src/app/admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts +++ b/src/app/admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts @@ -1,3 +1,4 @@ +import { AsyncPipe } from '@angular/common'; import { Component, OnInit, @@ -6,7 +7,10 @@ import { ActivatedRoute, Router, } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -16,6 +20,7 @@ import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model' import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths'; +import { FormatFormComponent } from '../format-form/format-form.component'; /** * This component renders the edit page of a bitstream format. @@ -24,6 +29,12 @@ import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-p @Component({ selector: 'ds-edit-bitstream-format', templateUrl: './edit-bitstream-format.component.html', + imports: [ + FormatFormComponent, + TranslateModule, + AsyncPipe, + ], + standalone: true, }) export class EditBitstreamFormatComponent implements OnInit { diff --git a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts index ceeb313a9d4..2b9f5034fe6 100644 --- a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts +++ b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts @@ -22,6 +22,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level'; import { isEmpty } from '../../../../shared/empty.util'; +import { FormComponent } from '../../../../shared/form/form.component'; import { RouterStub } from '../../../../shared/testing/router.stub'; import { FormatFormComponent } from './format-form.component'; @@ -52,13 +53,24 @@ describe('FormatFormComponent', () => { const initAsync = () => { TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), ReactiveFormsModule, FormsModule, TranslateModule.forRoot(), NgbModule], - declarations: [FormatFormComponent], - providers: [ - { provide: Router, useValue: router }, + imports: [ + CommonModule, + RouterTestingModule.withRoutes([]), + ReactiveFormsModule, + FormsModule, + TranslateModule.forRoot(), + NgbModule, + FormatFormComponent, ], + providers: [{ provide: Router, useValue: router }], schemas: [CUSTOM_ELEMENTS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(FormatFormComponent, { + remove: { + imports: [FormComponent], + }, + }) + .compileComponents(); }; const initBeforeEach = () => { diff --git a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts index 42a1e4bafff..08889ce28be 100644 --- a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts +++ b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts @@ -1,3 +1,4 @@ +import { NgIf } from '@angular/common'; import { Component, EventEmitter, @@ -11,12 +12,10 @@ import { DynamicFormArrayModel, DynamicFormControlLayout, DynamicFormControlModel, - DynamicFormService, DynamicInputModel, DynamicSelectModel, DynamicTextAreaModel, } from '@ng-dynamic-forms/core'; -import { TranslateService } from '@ngx-translate/core'; import { environment } from '../../../../../environments/environment'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; @@ -25,6 +24,7 @@ import { hasValue, isEmpty, } from '../../../../shared/empty.util'; +import { FormComponent } from '../../../../shared/form/form.component'; import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths'; /** @@ -33,6 +33,11 @@ import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-p @Component({ selector: 'ds-bitstream-format-form', templateUrl: './format-form.component.html', + imports: [ + FormComponent, + NgIf, + ], + standalone: true, }) export class FormatFormComponent implements OnInit { @@ -132,9 +137,7 @@ export class FormatFormComponent implements OnInit { }, this.arrayElementLayout), ]; - constructor(private dynamicFormService: DynamicFormService, - private translateService: TranslateService, - private router: Router) { + constructor(private router: Router) { } diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts index 63acf0f2586..8fe31a0fd47 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts @@ -10,25 +10,36 @@ import { waitForAsync, } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { RouterLink } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; +import { FormBuilderService } from 'src/app/shared/form/builder/form-builder.service'; import { RestResponse } from '../../../core/cache/response.models'; +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; import { buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { GroupDataService } from '../../../core/eperson/group-data.service'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { RegistryService } from '../../../core/registry/registry.service'; +import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; +import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; +import { FormService } from '../../../shared/form/form.service'; import { HostWindowService } from '../../../shared/host-window.service'; +import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; +import { getMockFormService } from '../../../shared/mocks/form-service.mock'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { SearchConfigurationServiceStub } from '../../../shared/testing/search-configuration-service.stub'; import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { MetadataRegistryComponent } from './metadata-registry.component'; +import { MetadataSchemaFormComponent } from './metadata-schema-form/metadata-schema-form.component'; describe('MetadataRegistryComponent', () => { let comp: MetadataRegistryComponent; @@ -76,20 +87,67 @@ describe('MetadataRegistryComponent', () => { paginationService = new PaginationServiceStub(); + const configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { + name: 'test', + values: [ + 'org.dspace.ctask.general.ProfileFormats = test', + ], + })), + }); + + const mockGroupService = jasmine.createSpyObj('groupService', + { + // findByHref: jasmine.createSpy('findByHref'), + // findAll: jasmine.createSpy('findAll'), + // searchGroups: jasmine.createSpy('searchGroups'), + getUUIDFromString: jasmine.createSpy('getUUIDFromString'), + }, + { + linkPath: 'groups', + }, + ); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [MetadataRegistryComponent, PaginationComponent, EnumKeysPipe], + imports: [ + CommonModule, + RouterTestingModule.withRoutes([]), + TranslateModule.forRoot(), + NgbModule, + MetadataRegistryComponent, + PaginationComponent, + EnumKeysPipe, + ], providers: [ { provide: RegistryService, useValue: registryServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: PaginationService, useValue: paginationService }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { + provide: NotificationsService, + useValue: new NotificationsServiceStub(), + }, + { provide: FormService, useValue: getMockFormService() }, + { provide: GroupDataService, useValue: mockGroupService }, + { + provide: ConfigurationDataService, + useValue: configurationDataService, + }, + { + provide: SearchConfigurationService, + useValue: new SearchConfigurationServiceStub(), + }, + { provide: FormBuilderService, useValue: getMockFormBuilderService() }, ], schemas: [NO_ERRORS_SCHEMA], - }).overrideComponent(MetadataRegistryComponent, { - set: { changeDetection: ChangeDetectionStrategy.Default }, - }).compileComponents(); + }) + .overrideComponent(MetadataRegistryComponent, { + remove: { + imports: [MetadataSchemaFormComponent, RouterLink], + }, + add: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts index 976ab572cc7..a148559bd2b 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts @@ -1,6 +1,18 @@ +import { + AsyncPipe, + NgClass, + NgForOf, + NgIf, +} from '@angular/common'; import { Component } from '@angular/core'; -import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + Router, + RouterLink, +} from '@angular/router'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { BehaviorSubject, combineLatest as observableCombineLatest, @@ -23,13 +35,26 @@ import { NoContent } from '../../../core/shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { MetadataSchemaFormComponent } from './metadata-schema-form/metadata-schema-form.component'; @Component({ selector: 'ds-metadata-registry', templateUrl: './metadata-registry.component.html', styleUrls: ['./metadata-registry.component.scss'], + imports: [ + MetadataSchemaFormComponent, + TranslateModule, + AsyncPipe, + PaginationComponent, + NgIf, + NgForOf, + NgClass, + RouterLink, + ], + standalone: true, }) /** * A component used for managing all existing metadata schemas within the repository. diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts index 17fdb6cd3d5..8a08b59800e 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts @@ -14,6 +14,8 @@ import { of as observableOf } from 'rxjs'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; +import { FormComponent } from '../../../../shared/form/form.component'; +import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; import { EnumKeysPipe } from '../../../../shared/utils/enum-keys-pipe'; import { MetadataSchemaFormComponent } from './metadata-schema-form.component'; @@ -44,14 +46,19 @@ describe('MetadataSchemaFormComponent', () => { beforeEach(waitForAsync(() => { return TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [MetadataSchemaFormComponent, EnumKeysPipe], + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, MetadataSchemaFormComponent, EnumKeysPipe], providers: [ { provide: RegistryService, useValue: registryServiceStub }, - { provide: FormBuilderService, useValue: formBuilderServiceStub }, + { provide: FormBuilderService, useValue: getMockFormBuilderService() }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(MetadataSchemaFormComponent, { + remove: { + imports: [FormComponent], + }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts index 640a9bff293..068c7f1bab4 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts @@ -1,3 +1,7 @@ +import { + AsyncPipe, + NgIf, +} from '@angular/common'; import { Component, EventEmitter, @@ -12,7 +16,10 @@ import { DynamicFormLayout, DynamicInputModel, } from '@ng-dynamic-forms/core'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { combineLatest, Observable, @@ -26,10 +33,18 @@ import { import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; +import { FormComponent } from '../../../../shared/form/form.component'; @Component({ selector: 'ds-metadata-schema-form', templateUrl: './metadata-schema-form.component.html', + imports: [ + NgIf, + AsyncPipe, + TranslateModule, + FormComponent, + ], + standalone: true, }) /** * A form used for creating and editing metadata schemas diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts index fb9037b5822..8044811ece0 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts @@ -15,6 +15,8 @@ import { MetadataField } from '../../../../core/metadata/metadata-field.model'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; +import { FormComponent } from '../../../../shared/form/form.component'; +import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; import { EnumKeysPipe } from '../../../../shared/utils/enum-keys-pipe'; import { MetadataFieldFormComponent } from './metadata-field-form.component'; @@ -54,14 +56,17 @@ describe('MetadataFieldFormComponent', () => { beforeEach(waitForAsync(() => { return TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [MetadataFieldFormComponent, EnumKeysPipe], + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule, MetadataFieldFormComponent, EnumKeysPipe], providers: [ { provide: RegistryService, useValue: registryServiceStub }, - { provide: FormBuilderService, useValue: formBuilderServiceStub }, + { provide: FormBuilderService, useValue: getMockFormBuilderService() }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(MetadataFieldFormComponent, { + remove: { imports: [FormComponent] }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts index 48451357a0e..bfadd018ef6 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts @@ -1,3 +1,7 @@ +import { + AsyncPipe, + NgIf, +} from '@angular/common'; import { Component, EventEmitter, @@ -14,7 +18,10 @@ import { DynamicInputModel, DynamicTextAreaModel, } from '@ng-dynamic-forms/core'; -import { TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { combineLatest } from 'rxjs'; import { take } from 'rxjs/operators'; @@ -22,10 +29,18 @@ import { MetadataField } from '../../../../core/metadata/metadata-field.model'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; +import { FormComponent } from '../../../../shared/form/form.component'; @Component({ selector: 'ds-metadata-field-form', templateUrl: './metadata-field-form.component.html', + imports: [ + NgIf, + FormComponent, + TranslateModule, + AsyncPipe, + ], + standalone: true, }) /** * A form used for creating and editing metadata fields diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts index 6c4e62e45f6..3276b1a3c77 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts @@ -17,11 +17,15 @@ import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; import { RestResponse } from '../../../core/cache/response.models'; +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; import { buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { GroupDataService } from '../../../core/eperson/group-data.service'; import { MetadataField } from '../../../core/metadata/metadata-field.model'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { RegistryService } from '../../../core/registry/registry.service'; +import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; +import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { HostWindowService } from '../../../shared/host-window.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { PaginationComponent } from '../../../shared/pagination/pagination.component'; @@ -31,8 +35,11 @@ import { HostWindowServiceStub } from '../../../shared/testing/host-window-servi import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; import { RouterStub } from '../../../shared/testing/router.stub'; +import { SearchConfigurationServiceStub } from '../../../shared/testing/search-configuration-service.stub'; +import { createPaginatedList } from '../../../shared/testing/utils.test'; import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { VarDirective } from '../../../shared/utils/var.directive'; +import { MetadataFieldFormComponent } from './metadata-field-form/metadata-field-form.component'; import { MetadataSchemaComponent } from './metadata-schema.component'; describe('MetadataSchemaComponent', () => { @@ -138,20 +145,56 @@ describe('MetadataSchemaComponent', () => { const paginationService = new PaginationServiceStub(); + const configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { + name: 'test', + values: [ + 'org.dspace.ctask.general.ProfileFormats = test', + ], + })), + }); + + const groupDataService = jasmine.createSpyObj('groupsDataService', { + findListByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), + getGroupRegistryRouterLink: '', + getUUIDFromString: '', + }); + + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], - declarations: [MetadataSchemaComponent, PaginationComponent, EnumKeysPipe, VarDirective], + imports: [ + CommonModule, + RouterTestingModule.withRoutes([]), + TranslateModule.forRoot(), + NgbModule, + MetadataSchemaComponent, + PaginationComponent, + EnumKeysPipe, + VarDirective, + ], providers: [ { provide: RegistryService, useValue: registryServiceStub }, { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: Router, useValue: new RouterStub() }, { provide: PaginationService, useValue: paginationService }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { + provide: NotificationsService, + useValue: new NotificationsServiceStub(), + }, + { provide: GroupDataService, useValue: groupDataService }, + { provide: ConfigurationDataService, useValue: configurationDataService }, + { provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() }, ], schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); + }) + .overrideComponent(MetadataSchemaComponent, { + remove: { + imports: [MetadataFieldFormComponent], + }, + }) + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts index 483aa9644e3..c5c1d650b1c 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts @@ -1,10 +1,22 @@ +import { + AsyncPipe, + NgClass, + NgForOf, + NgIf, +} from '@angular/common'; import { Component, OnDestroy, OnInit, } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; +import { + ActivatedRoute, + RouterLink, +} from '@angular/router'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { BehaviorSubject, combineLatest, @@ -32,13 +44,28 @@ import { } from '../../../core/shared/operators'; import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { VarDirective } from '../../../shared/utils/var.directive'; +import { MetadataFieldFormComponent } from './metadata-field-form/metadata-field-form.component'; @Component({ selector: 'ds-metadata-schema', templateUrl: './metadata-schema.component.html', styleUrls: ['./metadata-schema.component.scss'], + imports: [ + AsyncPipe, + VarDirective, + MetadataFieldFormComponent, + TranslateModule, + PaginationComponent, + NgIf, + NgForOf, + NgClass, + RouterLink, + ], + standalone: true, }) /** * A component used for managing all existing metadata fields within the current metadata schema. diff --git a/src/app/admin/admin-reports/admin-reports-routes.ts b/src/app/admin/admin-reports/admin-reports-routes.ts new file mode 100644 index 00000000000..be1f7cc7a09 --- /dev/null +++ b/src/app/admin/admin-reports/admin-reports-routes.ts @@ -0,0 +1,30 @@ +import { Route } from '@angular/router'; + +import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { FilteredCollectionsComponent } from './filtered-collections/filtered-collections.component'; +import { FilteredItemsComponent } from './filtered-items/filtered-items.component'; + +export const ROUTES: Route[] = [ + { + path: 'collections', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + data: { title: 'admin.reports.collections.title', breadcrumbKey: 'admin.reports.collections' }, + children: [ + { + path: '', + component: FilteredCollectionsComponent, + }, + ], + }, + { + path: 'queries', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + data: { title: 'admin.reports.items.title', breadcrumbKey: 'admin.reports.items' }, + children: [ + { + path: '', + component: FilteredItemsComponent, + }, + ], + }, +]; diff --git a/src/app/admin/admin-reports/admin-reports-routing.module.ts b/src/app/admin/admin-reports/admin-reports-routing.module.ts deleted file mode 100644 index e891fa9ad41..00000000000 --- a/src/app/admin/admin-reports/admin-reports-routing.module.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { FilteredCollectionsComponent } from './filtered-collections/filtered-collections.component'; -import { FilteredItemsComponent } from './filtered-items/filtered-items.component'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { - path: 'collections', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - data: { title: 'admin.reports.collections.title', breadcrumbKey: 'admin.reports.collections' }, - children: [ - { - path: '', - component: FilteredCollectionsComponent, - }, - ], - }, - { - path: 'queries', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - data: { title: 'admin.reports.items.title', breadcrumbKey: 'admin.reports.items' }, - children: [ - { - path: '', - component: FilteredItemsComponent, - }, - ], - }, - ]), - ], -}) -export class AdminReportsRoutingModule { - -} diff --git a/src/app/admin/admin-reports/admin-reports.module.ts b/src/app/admin/admin-reports/admin-reports.module.ts deleted file mode 100644 index 6eb713f0a41..00000000000 --- a/src/app/admin/admin-reports/admin-reports.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; - -import { FormModule } from '../../shared/form/form.module'; -import { SharedModule } from '../../shared/shared.module'; -import { AdminReportsRoutingModule } from './admin-reports-routing.module'; -import { FilteredCollectionsComponent } from './filtered-collections/filtered-collections.component'; -import { FilteredItemsComponent } from './filtered-items/filtered-items.component'; -import { FiltersComponent } from './filters-section/filters-section.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule, - FormModule, - AdminReportsRoutingModule, - NgbAccordionModule, - ], - declarations: [ - FilteredCollectionsComponent, - FilteredItemsComponent, - FiltersComponent, - ], -}) -export class AdminReportsModule { -} diff --git a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.spec.ts b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.spec.ts index 1fa350645bb..dff64452252 100644 --- a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.spec.ts +++ b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.spec.ts @@ -39,7 +39,6 @@ describe('FiltersComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [FilteredCollectionsComponent], imports: [ NgbAccordionModule, TranslateModule.forRoot({ @@ -49,6 +48,7 @@ describe('FiltersComponent', () => { }, }), HttpClientTestingModule, + FilteredCollectionsComponent, ], providers: [ FormBuilder, diff --git a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts index 84e334e971c..b4bebfc924f 100644 --- a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts +++ b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts @@ -1,3 +1,7 @@ +import { + KeyValuePipe, + NgForOf, +} from '@angular/common'; import { Component, ViewChild, @@ -6,7 +10,11 @@ import { FormBuilder, FormGroup, } from '@angular/forms'; -import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'; +import { + NgbAccordion, + NgbAccordionModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; import { Observable } from 'rxjs'; import { RestRequestMethod } from 'src/app/core/data/rest-request-method'; import { DspaceRestService } from 'src/app/core/dspace-rest/dspace-rest.service'; @@ -23,6 +31,14 @@ import { FilteredCollections } from './filtered-collections.model'; selector: 'ds-report-filtered-collections', templateUrl: './filtered-collections.component.html', styleUrls: ['./filtered-collections.component.scss'], + imports: [ + TranslateModule, + NgbAccordionModule, + FiltersComponent, + KeyValuePipe, + NgForOf, + ], + standalone: true, }) export class FilteredCollectionsComponent { diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts index c63880ae48b..9fd4643573e 100644 --- a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts +++ b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts @@ -1,3 +1,8 @@ +import { + AsyncPipe, + NgForOf, + NgIf, +} from '@angular/common'; import { Component, ViewChild, @@ -7,9 +12,16 @@ import { FormBuilder, FormControl, FormGroup, + ReactiveFormsModule, } from '@angular/forms'; -import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateService } from '@ngx-translate/core'; +import { + NgbAccordion, + NgbAccordionModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { map, Observable, @@ -43,6 +55,16 @@ import { QueryPredicate } from './query-predicate.model'; selector: 'ds-report-filtered-items', templateUrl: './filtered-items.component.html', styleUrls: ['./filtered-items.component.scss'], + imports: [ + ReactiveFormsModule, + NgbAccordionModule, + TranslateModule, + AsyncPipe, + NgIf, + NgForOf, + FiltersComponent, + ], + standalone: true, }) export class FilteredItemsComponent { diff --git a/src/app/admin/admin-reports/filters-section/filters-section.component.spec.ts b/src/app/admin/admin-reports/filters-section/filters-section.component.spec.ts index 6b8b069f93e..14bc6df593f 100644 --- a/src/app/admin/admin-reports/filters-section/filters-section.component.spec.ts +++ b/src/app/admin/admin-reports/filters-section/filters-section.component.spec.ts @@ -20,7 +20,6 @@ describe('FiltersComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [FiltersComponent], imports: [ TranslateModule.forRoot({ loader: { @@ -28,6 +27,7 @@ describe('FiltersComponent', () => { useClass: TranslateLoaderMock, }, }), + FiltersComponent, ], providers: [ FormBuilder, diff --git a/src/app/admin/admin-reports/filters-section/filters-section.component.ts b/src/app/admin/admin-reports/filters-section/filters-section.component.ts index a739d849f8a..85b7932ab43 100644 --- a/src/app/admin/admin-reports/filters-section/filters-section.component.ts +++ b/src/app/admin/admin-reports/filters-section/filters-section.component.ts @@ -1,3 +1,4 @@ +import { NgForOf } from '@angular/common'; import { Component, Input, @@ -6,7 +7,9 @@ import { FormBuilder, FormControl, FormGroup, + ReactiveFormsModule, } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; import { Filter } from './filter.model'; import { FilterGroup } from './filter-group.model'; @@ -19,6 +22,12 @@ import { FilterGroup } from './filter-group.model'; selector: 'ds-filters', templateUrl: './filters-section.component.html', styleUrls: ['./filters-section.component.scss'], + imports: [ + NgForOf, + ReactiveFormsModule, + TranslateModule, + ], + standalone: true, }) export class FiltersComponent { diff --git a/src/app/admin/admin-routes.ts b/src/app/admin/admin-routes.ts new file mode 100644 index 00000000000..a89dbb2c0a9 --- /dev/null +++ b/src/app/admin/admin-routes.ts @@ -0,0 +1,85 @@ +import { Route } from '@angular/router'; + +import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component'; +import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component'; +import { MetadataImportPageComponent } from './admin-import-metadata-page/metadata-import-page.component'; +import { + LDN_PATH, + NOTIFICATIONS_MODULE_PATH, + NOTIFY_DASHBOARD_MODULE_PATH, + REGISTRIES_MODULE_PATH, + REPORTS_MODULE_PATH, +} from './admin-routing-paths'; +import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component'; +import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component'; + +export const ROUTES: Route[] = [ + { + path: NOTIFICATIONS_MODULE_PATH, + loadChildren: () => import('./admin-notifications/admin-notifications-routes') + .then((m) => m.ROUTES), + }, + { + path: REGISTRIES_MODULE_PATH, + loadChildren: () => import('./admin-registries/admin-registries-routes') + .then((m) => m.ROUTES), + }, + { + path: 'search', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + component: AdminSearchPageComponent, + data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' }, + }, + { + path: 'workflow', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + component: AdminWorkflowPageComponent, + data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' }, + }, + { + path: 'curation-tasks', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + component: AdminCurationTasksComponent, + data: { title: 'admin.curation-tasks.title', breadcrumbKey: 'admin.curation-tasks' }, + }, + { + path: 'metadata-import', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + component: MetadataImportPageComponent, + data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' }, + }, + { + path: 'batch-import', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + component: BatchImportPageComponent, + data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' }, + }, + { + path: 'system-wide-alert', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + loadChildren: () => import('../system-wide-alert/system-wide-alert-routes').then((m) => m.ROUTES), + data: { title: 'admin.system-wide-alert.title', breadcrumbKey: 'admin.system-wide-alert' }, + }, + { + path: LDN_PATH, + children: [ + { path: '', pathMatch: 'full', redirectTo: 'services' }, + { + path: 'services', + loadChildren: () => import('./admin-ldn-services/admin-ldn-services-routes') + .then((m) => m.ROUTES), + }, + ], + }, + { + path: REPORTS_MODULE_PATH, + loadChildren: () => import('./admin-reports/admin-reports-routes') + .then((m) => m.ROUTES), + }, + { + path: NOTIFY_DASHBOARD_MODULE_PATH, + loadChildren: () => import('./admin-notify-dashboard/admin-notify-dashboard-routes') + .then((m) => m.ROUTES), + }, +]; diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts deleted file mode 100644 index d9255e64822..00000000000 --- a/src/app/admin/admin-routing.module.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service'; -import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; -import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component'; -import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component'; -import { MetadataImportPageComponent } from './admin-import-metadata-page/metadata-import-page.component'; -import { - LDN_PATH, - NOTIFICATIONS_MODULE_PATH, - NOTIFY_DASHBOARD_MODULE_PATH, - REGISTRIES_MODULE_PATH, - REPORTS_MODULE_PATH, -} from './admin-routing-paths'; -import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component'; -import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { - path: NOTIFICATIONS_MODULE_PATH, - loadChildren: () => import('./admin-notifications/admin-notifications.module') - .then((m) => m.AdminNotificationsModule), - }, - { - path: REGISTRIES_MODULE_PATH, - loadChildren: () => import('./admin-registries/admin-registries.module') - .then((m) => m.AdminRegistriesModule), - canActivate: [SiteAdministratorGuard], - }, - { - path: 'search', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - component: AdminSearchPageComponent, - data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: 'workflow', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - component: AdminWorkflowPageComponent, - data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: 'curation-tasks', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - component: AdminCurationTasksComponent, - data: { title: 'admin.curation-tasks.title', breadcrumbKey: 'admin.curation-tasks' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: 'metadata-import', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - component: MetadataImportPageComponent, - data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: 'batch-import', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - component: BatchImportPageComponent, - data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: 'system-wide-alert', - resolve: { breadcrumb: I18nBreadcrumbResolver }, - loadChildren: () => import('../system-wide-alert/system-wide-alert.module').then((m) => m.SystemWideAlertModule), - data: { title: 'admin.system-wide-alert.title', breadcrumbKey: 'admin.system-wide-alert' }, - canActivate: [SiteAdministratorGuard], - }, - { - path: LDN_PATH, - children: [ - { path: '', pathMatch: 'full', redirectTo: 'services' }, - { - path: 'services', - loadChildren: () => import('./admin-ldn-services/admin-ldn-services.module') - .then((m) => m.AdminLdnServicesModule), - }, - ], - }, - { - path: REPORTS_MODULE_PATH, - loadChildren: () => import('./admin-reports/admin-reports.module') - .then((m) => m.AdminReportsModule), - }, - { - path: NOTIFY_DASHBOARD_MODULE_PATH, - loadChildren: () => import('./admin-notify-dashboard/admin-notify-dashboard.module') - .then((m) => m.AdminNotifyDashboardModule), - }, - ]), - ], - providers: [ - I18nBreadcrumbResolver, - I18nBreadcrumbsService, - ], -}) -export class AdminRoutingModule { - -} diff --git a/src/app/admin/admin-search-page/admin-search-page.component.html b/src/app/admin/admin-search-page/admin-search-page.component.html index 69ff132fe3f..516799ddf90 100644 --- a/src/app/admin/admin-search-page/admin-search-page.component.html +++ b/src/app/admin/admin-search-page/admin-search-page.component.html @@ -1 +1 @@ - + diff --git a/src/app/admin/admin-search-page/admin-search-page.component.spec.ts b/src/app/admin/admin-search-page/admin-search-page.component.spec.ts index 43c7d86a08a..d3a39f12f4a 100644 --- a/src/app/admin/admin-search-page/admin-search-page.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-page.component.spec.ts @@ -4,17 +4,27 @@ import { TestBed, waitForAsync, } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { ThemedConfigurationSearchPageComponent } from '../../search-page/themed-configuration-search-page.component'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { AdminSearchPageComponent } from './admin-search-page.component'; describe('AdminSearchPageComponent', () => { let component: AdminSearchPageComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [AdminSearchPageComponent], + beforeEach(waitForAsync(async () => { + await TestBed.configureTestingModule({ + imports: [AdminSearchPageComponent], + providers: [ + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, + ], schemas: [NO_ERRORS_SCHEMA], + }).overrideComponent(AdminSearchPageComponent, { + remove: { + imports: [ThemedConfigurationSearchPageComponent], + }, }) .compileComponents(); })); diff --git a/src/app/admin/admin-search-page/admin-search-page.component.ts b/src/app/admin/admin-search-page/admin-search-page.component.ts index 6508b20efa8..99909b8257f 100644 --- a/src/app/admin/admin-search-page/admin-search-page.component.ts +++ b/src/app/admin/admin-search-page/admin-search-page.component.ts @@ -1,11 +1,14 @@ import { Component } from '@angular/core'; import { Context } from '../../core/shared/context.model'; +import { ThemedConfigurationSearchPageComponent } from '../../search-page/themed-configuration-search-page.component'; @Component({ selector: 'ds-admin-search-page', templateUrl: './admin-search-page.component.html', styleUrls: ['./admin-search-page.component.scss'], + standalone: true, + imports: [ThemedConfigurationSearchPageComponent], }) /** diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts index 249c6b15bb8..470b3a52717 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts @@ -20,7 +20,6 @@ import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucata import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model'; -import { SharedModule } from '../../../../../shared/shared.module'; import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub'; import { FileServiceStub } from '../../../../../shared/testing/file-service.stub'; @@ -52,9 +51,8 @@ describe('CollectionAdminSearchResultGridElementComponent', () => { NoopAnimationsModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), - SharedModule, + CollectionAdminSearchResultGridElementComponent, ], - declarations: [CollectionAdminSearchResultGridElementComponent], providers: [ { provide: TruncatableService, useValue: mockTruncatableService }, { provide: BitstreamDataService, useValue: {} }, diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts index 5d39b79da54..172226dd076 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths'; import { Collection } from '../../../../../core/shared/collection.model'; @@ -6,6 +7,7 @@ import { Context } from '../../../../../core/shared/context.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { CollectionSearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component'; import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component'; @listableObjectComponent(CollectionSearchResult, ViewMode.GridElement, Context.AdminSearch) @@ -13,6 +15,8 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g selector: 'ds-collection-admin-search-result-list-element', styleUrls: ['./collection-admin-search-result-grid-element.component.scss'], templateUrl: './collection-admin-search-result-grid-element.component.html', + standalone: true, + imports: [CollectionSearchResultGridElementComponent, RouterLink], }) /** * The component for displaying a list element for a collection search result on the admin search page diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts index 137f5be5f29..68210256534 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts @@ -21,7 +21,6 @@ import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucata import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; -import { SharedModule } from '../../../../../shared/shared.module'; import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub'; import { FileServiceStub } from '../../../../../shared/testing/file-service.stub'; @@ -53,9 +52,8 @@ describe('CommunityAdminSearchResultGridElementComponent', () => { NoopAnimationsModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), - SharedModule, + CommunityAdminSearchResultGridElementComponent, ], - declarations: [CommunityAdminSearchResultGridElementComponent], providers: [ { provide: TruncatableService, useValue: mockTruncatableService }, { provide: BitstreamDataService, useValue: {} }, diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts index 9dd672e54cf..50be35229dd 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths'; import { Community } from '../../../../../core/shared/community.model'; @@ -6,6 +7,7 @@ import { Context } from '../../../../../core/shared/context.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { CommunitySearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component'; import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component'; @listableObjectComponent(CommunitySearchResult, ViewMode.GridElement, Context.AdminSearch) @@ -13,6 +15,8 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g selector: 'ds-community-admin-search-result-grid-element', styleUrls: ['./community-admin-search-result-grid-element.component.scss'], templateUrl: './community-admin-search-result-grid-element.component.html', + standalone: true, + imports: [CommunitySearchResultGridElementComponent, RouterLink], }) /** * The component for displaying a list element for a community search result on the admin search page diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts index 80bd4469f62..3eb8d9caf7d 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts @@ -17,6 +17,7 @@ import { RemoteData } from '../../../../../core/data/remote-data'; import { Bitstream } from '../../../../../core/shared/bitstream.model'; import { FileService } from '../../../../../core/shared/file.service'; import { Item } from '../../../../../core/shared/item.model'; +import { ListableModule } from '../../../../../core/shared/listable.module'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service'; import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; @@ -24,7 +25,6 @@ import { CollectionElementLinkType } from '../../../../../shared/object-collecti import { AccessStatusObject } from '../../../../../shared/object-collection/shared/badges/access-status-badge/access-status.model'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; -import { SharedModule } from '../../../../../shared/shared.module'; import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub'; import { FileServiceStub } from '../../../../../shared/testing/file-service.stub'; @@ -63,12 +63,12 @@ describe('ItemAdminSearchResultGridElementComponent', () => { init(); TestBed.configureTestingModule( { - declarations: [ItemAdminSearchResultGridElementComponent], imports: [ NoopAnimationsModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), - SharedModule, + ListableModule, + ItemAdminSearchResultGridElementComponent, ], providers: [ { provide: TruncatableService, useValue: mockTruncatableService }, diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts index b337df8ed20..fd5e641f524 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts @@ -23,12 +23,15 @@ import { import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { ItemAdminSearchResultActionsComponent } from '../../item-admin-search-result-actions.component'; @listableObjectComponent(ItemSearchResult, ViewMode.GridElement, Context.AdminSearch) @Component({ selector: 'ds-item-admin-search-result-grid-element', styleUrls: ['./item-admin-search-result-grid-element.component.scss'], templateUrl: './item-admin-search-result-grid-element.component.html', + standalone: true, + imports: [ItemAdminSearchResultActionsComponent, DynamicComponentLoaderDirective], }) /** * The component for displaying a list element for an item search result on the admin search page diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.html b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.html index e51e207bbee..6c8342d2e6e 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.html +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.html @@ -3,7 +3,7 @@ [linkType]="linkType" [listID]="listID"> diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts index cd7e7a237b6..7a4e2da68da 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts @@ -15,8 +15,12 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service import { Collection } from '../../../../../core/shared/collection.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; +import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service'; +import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model'; +import { CollectionSearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component'; +import { ThemeService } from '../../../../../shared/theme-support/theme.service'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { CollectionAdminSearchResultListElementComponent } from './collection-admin-search-result-list-element.component'; @@ -39,14 +43,22 @@ describe('CollectionAdminSearchResultListElementComponent', () => { imports: [ TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), + CollectionAdminSearchResultListElementComponent, ], - declarations: [CollectionAdminSearchResultListElementComponent], - providers: [{ provide: TruncatableService, useValue: {} }, + providers: [ + { provide: TruncatableService, useValue: mockTruncatableService }, { provide: DSONameService, useClass: DSONameServiceMock }, - { provide: APP_CONFIG, useValue: environment }], + { provide: APP_CONFIG, useValue: environment }, + { provide: ThemeService, useValue: getMockThemeService() }, + ], schemas: [NO_ERRORS_SCHEMA], - }) - .compileComponents(); + }).overrideComponent(CollectionAdminSearchResultListElementComponent, { + remove: { + imports: [ + CollectionSearchResultListElementComponent, + ], + }, + }).compileComponents(); })); beforeEach(() => { @@ -64,7 +76,7 @@ describe('CollectionAdminSearchResultListElementComponent', () => { }); it('should render an edit button with the correct link', () => { - const a = fixture.debugElement.query(By.css('a')); + const a = fixture.debugElement.query(By.css('a[data-test="coll-link"]')); const link = a.nativeElement.href; expect(link).toContain(getCollectionEditRoute(id)); }); diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts index 4ca4c3af4db..37afbf14fee 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts @@ -1,4 +1,6 @@ import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths'; import { Collection } from '../../../../../core/shared/collection.model'; @@ -6,6 +8,7 @@ import { Context } from '../../../../../core/shared/context.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { CollectionSearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component'; import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; @listableObjectComponent(CollectionSearchResult, ViewMode.ListElement, Context.AdminSearch) @@ -13,6 +16,8 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l selector: 'ds-collection-admin-search-result-list-element', styleUrls: ['./collection-admin-search-result-list-element.component.scss'], templateUrl: './collection-admin-search-result-list-element.component.html', + standalone: true, + imports: [CollectionSearchResultListElementComponent, RouterLink, TranslateModule], }) /** * The component for displaying a list element for a collection search result on the admin search page diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts index 3c1315cc640..04077bf590f 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts @@ -15,8 +15,10 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service import { Community } from '../../../../../core/shared/community.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; +import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service'; import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; +import { CommunitySearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { CommunityAdminSearchResultListElementComponent } from './community-admin-search-result-list-element.component'; @@ -39,13 +41,20 @@ describe('CommunityAdminSearchResultListElementComponent', () => { imports: [ TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), + CommunityAdminSearchResultListElementComponent, ], - declarations: [CommunityAdminSearchResultListElementComponent], - providers: [{ provide: TruncatableService, useValue: {} }, + providers: [ + { provide: TruncatableService, useValue: mockTruncatableService }, { provide: DSONameService, useClass: DSONameServiceMock }, - { provide: APP_CONFIG, useValue: environment }], + { provide: APP_CONFIG, useValue: environment }, + ], schemas: [NO_ERRORS_SCHEMA], }) + .overrideComponent(CommunityAdminSearchResultListElementComponent, { + remove: { + imports: [CommunitySearchResultListElementComponent], + }, + }) .compileComponents(); })); @@ -56,6 +65,7 @@ describe('CommunityAdminSearchResultListElementComponent', () => { component.linkTypes = CollectionElementLinkType; component.index = 0; component.viewModes = ViewMode; + component.ngOnInit(); fixture.detectChanges(); }); diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts index a3f8b42a139..5861f15c1ff 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts @@ -1,4 +1,6 @@ import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths'; import { Community } from '../../../../../core/shared/community.model'; @@ -6,6 +8,7 @@ import { Context } from '../../../../../core/shared/context.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { CommunitySearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component'; import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; @listableObjectComponent(CommunitySearchResult, ViewMode.ListElement, Context.AdminSearch) @@ -13,6 +16,8 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l selector: 'ds-community-admin-search-result-list-element', styleUrls: ['./community-admin-search-result-list-element.component.scss'], templateUrl: './community-admin-search-result-list-element.component.html', + standalone: true, + imports: [CommunitySearchResultListElementComponent, RouterLink, TranslateModule], }) /** * The component for displaying a list element for a community search result on the admin search page diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts index f34c2709d86..a3631473e94 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts @@ -13,9 +13,12 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service import { Item } from '../../../../../core/shared/item.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; +import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service'; import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { ListableObjectComponentLoaderComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object-component-loader.component'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { ItemAdminSearchResultActionsComponent } from '../../item-admin-search-result-actions.component'; import { ItemAdminSearchResultListElementComponent } from './item-admin-search-result-list-element.component'; describe('ItemAdminSearchResultListElementComponent', () => { @@ -37,13 +40,18 @@ describe('ItemAdminSearchResultListElementComponent', () => { imports: [ TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), + ItemAdminSearchResultListElementComponent, ], - declarations: [ItemAdminSearchResultListElementComponent], - providers: [{ provide: TruncatableService, useValue: {} }, + providers: [ + { provide: TruncatableService, useValue: mockTruncatableService }, { provide: DSONameService, useClass: DSONameServiceMock }, - { provide: APP_CONFIG, useValue: environment }], + { provide: APP_CONFIG, useValue: environment }, + ], schemas: [NO_ERRORS_SCHEMA], }) + .overrideComponent(ItemAdminSearchResultListElementComponent, { + remove: { imports: [ListableObjectComponentLoaderComponent, ItemAdminSearchResultActionsComponent] }, + }) .compileComponents(); })); diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.ts index c181640c2c7..d77e86689a1 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.ts @@ -5,13 +5,17 @@ import { Item } from '../../../../../core/shared/item.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { ListableObjectComponentLoaderComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object-component-loader.component'; import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; +import { ItemAdminSearchResultActionsComponent } from '../../item-admin-search-result-actions.component'; @listableObjectComponent(ItemSearchResult, ViewMode.ListElement, Context.AdminSearch) @Component({ selector: 'ds-item-admin-search-result-list-element', styleUrls: ['./item-admin-search-result-list-element.component.scss'], templateUrl: './item-admin-search-result-list-element.component.html', + standalone: true, + imports: [ListableObjectComponentLoaderComponent, ItemAdminSearchResultActionsComponent], }) /** * The component for displaying a list element for an item search result on the admin search page diff --git a/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.spec.ts index 33a2cacb80d..c598c5b40d7 100644 --- a/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.spec.ts @@ -39,8 +39,8 @@ describe('ItemAdminSearchResultActionsComponent', () => { imports: [ TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), + ItemAdminSearchResultActionsComponent, ], - declarations: [ItemAdminSearchResultActionsComponent], schemas: [NO_ERRORS_SCHEMA], }) .compileComponents(); diff --git a/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts b/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts index 54f6c82f3bb..89d51481d7c 100644 --- a/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts @@ -1,7 +1,13 @@ +import { + NgClass, + NgIf, +} from '@angular/common'; import { Component, Input, } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { Item } from '../../../core/shared/item.model'; import { URLCombiner } from '../../../core/url-combiner/url-combiner'; @@ -19,6 +25,8 @@ import { getItemEditRoute } from '../../../item-page/item-page-routing-paths'; selector: 'ds-item-admin-search-result-actions-element', styleUrls: ['./item-admin-search-result-actions.component.scss'], templateUrl: './item-admin-search-result-actions.component.html', + standalone: true, + imports: [NgClass, RouterLink, NgIf, TranslateModule], }) /** * The component for displaying the actions for a list element for an item search result on the admin search page diff --git a/src/app/admin/admin-search-page/admin-search.module.ts b/src/app/admin/admin-search-page/admin-search.module.ts deleted file mode 100644 index f8eec908ff5..00000000000 --- a/src/app/admin/admin-search-page/admin-search.module.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { NgModule } from '@angular/core'; - -import { JournalEntitiesModule } from '../../entity-groups/journal-entities/journal-entities.module'; -import { ResearchEntitiesModule } from '../../entity-groups/research-entities/research-entities.module'; -import { SearchModule } from '../../shared/search/search.module'; -import { SharedModule } from '../../shared/shared.module'; -import { AdminSearchPageComponent } from './admin-search-page.component'; -import { CollectionAdminSearchResultGridElementComponent } from './admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component'; -import { CommunityAdminSearchResultGridElementComponent } from './admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component'; -import { ItemAdminSearchResultGridElementComponent } from './admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component'; -import { CollectionAdminSearchResultListElementComponent } from './admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component'; -import { CommunityAdminSearchResultListElementComponent } from './admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component'; -import { ItemAdminSearchResultListElementComponent } from './admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component'; -import { ItemAdminSearchResultActionsComponent } from './admin-search-results/item-admin-search-result-actions.component'; - -const ENTRY_COMPONENTS = [ - // put only entry components that use custom decorator - ItemAdminSearchResultListElementComponent, - CommunityAdminSearchResultListElementComponent, - CollectionAdminSearchResultListElementComponent, - ItemAdminSearchResultGridElementComponent, - CommunityAdminSearchResultGridElementComponent, - CollectionAdminSearchResultGridElementComponent, - ItemAdminSearchResultActionsComponent, -]; - -@NgModule({ - imports: [ - SearchModule, - SharedModule.withEntryComponents(), - JournalEntitiesModule.withEntryComponents(), - ResearchEntitiesModule.withEntryComponents(), - ], - declarations: [ - AdminSearchPageComponent, - ...ENTRY_COMPONENTS, - ], -}) -export class AdminSearchModule { - /** - * NOTE: this method allows to resolve issue with components that using a custom decorator - * which are not loaded during SSR otherwise - */ - static withEntryComponents() { - return { - ngModule: SharedModule, - providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })), - }; - } -} diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts index 3131899c494..fdee7c70e4b 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts @@ -25,19 +25,13 @@ describe('AdminSidebarSectionComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot()], - declarations: [AdminSidebarSectionComponent, TestComponent], + imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot(), AdminSidebarSectionComponent, TestComponent], providers: [ { provide: 'sectionDataProvider', useValue: { model: { link: 'google.com' }, icon: iconString } }, { provide: MenuService, useValue: menuService }, { provide: CSSVariableService, useClass: CSSVariableServiceStub }, ], - }).overrideComponent(AdminSidebarSectionComponent, { - set: { - entryComponents: [TestComponent], - }, - }) - .compileComponents(); + }).compileComponents(); })); beforeEach(() => { @@ -65,19 +59,13 @@ describe('AdminSidebarSectionComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot()], - declarations: [AdminSidebarSectionComponent, TestComponent], + imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot(), AdminSidebarSectionComponent, TestComponent], providers: [ { provide: 'sectionDataProvider', useValue: { model: { link: 'google.com', disabled: true }, icon: iconString } }, { provide: MenuService, useValue: menuService }, { provide: CSSVariableService, useClass: CSSVariableServiceStub }, ], - }).overrideComponent(AdminSidebarSectionComponent, { - set: { - entryComponents: [TestComponent], - }, - }) - .compileComponents(); + }).compileComponents(); })); beforeEach(() => { @@ -107,6 +95,8 @@ describe('AdminSidebarSectionComponent', () => { @Component({ selector: 'ds-test-cmp', template: ``, + standalone: true, + imports: [RouterTestingModule], }) class TestComponent { } diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts index d677c7f8baf..ff9897ce9e8 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts @@ -1,16 +1,20 @@ +import { NgClass } from '@angular/common'; import { Component, Inject, Injector, OnInit, } from '@angular/core'; -import { Router } from '@angular/router'; +import { + Router, + RouterLink, +} from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; import { isEmpty } from '../../../shared/empty.util'; import { MenuService } from '../../../shared/menu/menu.service'; import { MenuID } from '../../../shared/menu/menu-id.model'; import { LinkMenuItemModel } from '../../../shared/menu/menu-item/models/link.model'; -import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator'; import { MenuSection } from '../../../shared/menu/menu-section.model'; import { MenuSectionComponent } from '../../../shared/menu/menu-section/menu-section.component'; @@ -21,9 +25,10 @@ import { MenuSectionComponent } from '../../../shared/menu/menu-section/menu-sec selector: 'ds-admin-sidebar-section', templateUrl: './admin-sidebar-section.component.html', styleUrls: ['./admin-sidebar-section.component.scss'], + standalone: true, + imports: [NgClass, RouterLink, TranslateModule], }) -@rendersSectionForMenu(MenuID.ADMIN, false) export class AdminSidebarSectionComponent extends MenuSectionComponent implements OnInit { /** diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts index b8de30031ba..25892be2915 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts @@ -14,7 +14,10 @@ import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { + NgbModal, + NgbModalRef, +} from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; @@ -60,6 +63,12 @@ describe('AdminSidebarComponent', () => { children: [], }; + const mockNgbModal = { + open: jasmine.createSpy('open').and.returnValue( + { componentInstance: {}, closed: observableOf({}) } as NgbModalRef, + ), + }; + beforeEach(waitForAsync(() => { authorizationService = jasmine.createSpyObj('authorizationService', { @@ -67,8 +76,7 @@ describe('AdminSidebarComponent', () => { }); scriptService = jasmine.createSpyObj('scriptService', { scriptWithNameExistsAndCanExecute: observableOf(true) }); TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule], - declarations: [AdminSidebarComponent], + imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule, AdminSidebarComponent], providers: [ Injector, { provide: ThemeService, useValue: getMockThemeService() }, @@ -79,12 +87,7 @@ describe('AdminSidebarComponent', () => { { provide: AuthorizationDataService, useValue: authorizationService }, { provide: ScriptDataService, useValue: scriptService }, { provide: ActivatedRoute, useValue: routeStub }, - { - provide: NgbModal, useValue: { - open: () => {/*comment*/ - }, - }, - }, + { provide: NgbModal, useValue: mockNgbModal }, ], schemas: [NO_ERRORS_SCHEMA], }).overrideComponent(AdminSidebarComponent, { diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index 37bc8bbf986..cd26a11995e 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -1,3 +1,10 @@ +import { + AsyncPipe, + NgClass, + NgComponentOutlet, + NgFor, + NgIf, +} from '@angular/common'; import { Component, HostListener, @@ -6,6 +13,8 @@ import { OnInit, } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; import { BehaviorSubject, combineLatest, @@ -36,6 +45,8 @@ import { ThemeService } from '../../shared/theme-support/theme.service'; templateUrl: './admin-sidebar.component.html', styleUrls: ['./admin-sidebar.component.scss'], animations: [slideSidebar], + standalone: true, + imports: [NgIf, NgbDropdownModule, NgClass, NgFor, NgComponentOutlet, AsyncPipe, TranslateModule], }) export class AdminSidebarComponent extends MenuComponent implements OnInit { /** diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html index 636938b7b6e..1febfc23040 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html @@ -35,7 +35,6 @@