mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'upstream/dspace-8_x' into w2p-131442_backport-3888-to-dspace-8_x
This commit is contained in:
107
.eslintrc.json
107
.eslintrc.json
@@ -8,7 +8,15 @@
|
|||||||
"eslint-plugin-deprecation",
|
"eslint-plugin-deprecation",
|
||||||
"unused-imports",
|
"unused-imports",
|
||||||
"eslint-plugin-lodash",
|
"eslint-plugin-lodash",
|
||||||
"eslint-plugin-jsonc"
|
"eslint-plugin-jsonc",
|
||||||
|
"eslint-plugin-rxjs",
|
||||||
|
"eslint-plugin-simple-import-sort",
|
||||||
|
"eslint-plugin-import-newlines",
|
||||||
|
"dspace-angular-ts",
|
||||||
|
"dspace-angular-html"
|
||||||
|
],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"lint/test/fixture"
|
||||||
],
|
],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
@@ -18,7 +26,8 @@
|
|||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"project": [
|
"project": [
|
||||||
"./tsconfig.json",
|
"./tsconfig.json",
|
||||||
"./cypress/tsconfig.json"
|
"./cypress/tsconfig.json",
|
||||||
|
"./lint/tsconfig.json"
|
||||||
],
|
],
|
||||||
"createDefaultProgram": true
|
"createDefaultProgram": true
|
||||||
},
|
},
|
||||||
@@ -27,17 +36,32 @@
|
|||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
"plugin:@angular-eslint/recommended",
|
"plugin:@angular-eslint/recommended",
|
||||||
"plugin:@angular-eslint/template/process-inline-templates"
|
"plugin:@angular-eslint/template/process-inline-templates",
|
||||||
|
"plugin:rxjs/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"indent": [
|
||||||
|
"error",
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
"SwitchCase": 1,
|
||||||
|
"ignoredNodes": [
|
||||||
|
"ClassBody.body > PropertyDefinition[decorators.length > 0] > .key"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"max-classes-per-file": [
|
"max-classes-per-file": [
|
||||||
"error",
|
"error",
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
"comma-dangle": [
|
"comma-dangle": [
|
||||||
"off",
|
"error",
|
||||||
"always-multiline"
|
"always-multiline"
|
||||||
],
|
],
|
||||||
|
"object-curly-spacing": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
"eol-last": [
|
"eol-last": [
|
||||||
"error",
|
"error",
|
||||||
"always"
|
"always"
|
||||||
@@ -104,15 +128,13 @@
|
|||||||
"allowTernary": true
|
"allowTernary": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"prefer-const": "off", // todo: re-enable & fix errors (more strict than it used to be in TSLint)
|
"prefer-const": "error",
|
||||||
|
"no-case-declarations": "error",
|
||||||
|
"no-extra-boolean-cast": "error",
|
||||||
"prefer-spread": "off",
|
"prefer-spread": "off",
|
||||||
"no-underscore-dangle": "off",
|
"no-underscore-dangle": "off",
|
||||||
|
|
||||||
// todo: disabled rules from eslint:recommended, consider re-enabling & fixing
|
|
||||||
"no-prototype-builtins": "off",
|
"no-prototype-builtins": "off",
|
||||||
"no-useless-escape": "off",
|
"no-useless-escape": "off",
|
||||||
"no-case-declarations": "off",
|
|
||||||
"no-extra-boolean-cast": "off",
|
|
||||||
|
|
||||||
"@angular-eslint/directive-selector": [
|
"@angular-eslint/directive-selector": [
|
||||||
"error",
|
"error",
|
||||||
@@ -139,10 +161,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@angular-eslint/no-attribute-decorator": "error",
|
"@angular-eslint/no-attribute-decorator": "error",
|
||||||
"@angular-eslint/no-forward-ref": "error",
|
|
||||||
"@angular-eslint/no-output-native": "warn",
|
"@angular-eslint/no-output-native": "warn",
|
||||||
"@angular-eslint/no-output-on-prefix": "warn",
|
"@angular-eslint/no-output-on-prefix": "warn",
|
||||||
"@angular-eslint/no-conflicting-lifecycle": "warn",
|
"@angular-eslint/no-conflicting-lifecycle": "warn",
|
||||||
|
"@angular-eslint/use-lifecycle-interface": "error",
|
||||||
|
|
||||||
"@typescript-eslint/no-inferrable-types":[
|
"@typescript-eslint/no-inferrable-types":[
|
||||||
"error",
|
"error",
|
||||||
@@ -183,7 +205,7 @@
|
|||||||
],
|
],
|
||||||
"@typescript-eslint/type-annotation-spacing": "error",
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
"@typescript-eslint/unified-signatures": "error",
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
"@typescript-eslint/ban-types": "warn", // todo: deal with {} type issues & re-enable
|
"@typescript-eslint/ban-types": "error",
|
||||||
"@typescript-eslint/no-floating-promises": "warn",
|
"@typescript-eslint/no-floating-promises": "warn",
|
||||||
"@typescript-eslint/no-misused-promises": "warn",
|
"@typescript-eslint/no-misused-promises": "warn",
|
||||||
"@typescript-eslint/restrict-plus-operands": "warn",
|
"@typescript-eslint/restrict-plus-operands": "warn",
|
||||||
@@ -200,17 +222,65 @@
|
|||||||
"@typescript-eslint/no-unsafe-return": "off",
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
"@typescript-eslint/restrict-template-expressions": "off",
|
"@typescript-eslint/restrict-template-expressions": "off",
|
||||||
"@typescript-eslint/require-await": "off",
|
"@typescript-eslint/require-await": "off",
|
||||||
|
"@typescript-eslint/no-base-to-string": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoredTypeNames": [
|
||||||
|
"ResourceType",
|
||||||
|
"Error"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
"deprecation/deprecation": "warn",
|
"deprecation/deprecation": "warn",
|
||||||
|
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
"import/order": "off",
|
"import/order": "off",
|
||||||
|
"import/first": "error",
|
||||||
|
"import/newline-after-import": "error",
|
||||||
|
"import/no-duplicates": "error",
|
||||||
"import/no-deprecated": "warn",
|
"import/no-deprecated": "warn",
|
||||||
"import/no-namespace": "error",
|
"import/no-namespace": "error",
|
||||||
|
"import-newlines/enforce": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"items": 1,
|
||||||
|
"semi": true,
|
||||||
|
"forceSingleLine": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
"unused-imports/no-unused-imports": "error",
|
"unused-imports/no-unused-imports": "error",
|
||||||
"lodash/import-scope": [
|
"lodash/import-scope": [
|
||||||
"error",
|
"error",
|
||||||
"method"
|
"method"
|
||||||
]
|
],
|
||||||
|
|
||||||
|
"rxjs/no-nested-subscribe": "off", // todo: go over _all_ cases
|
||||||
|
|
||||||
|
// Custom DSpace Angular rules
|
||||||
|
"dspace-angular-ts/themed-component-classes": "error",
|
||||||
|
"dspace-angular-ts/themed-component-selectors": "error",
|
||||||
|
"dspace-angular-ts/themed-component-usages": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.spec.ts"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"project": [
|
||||||
|
"./tsconfig.json",
|
||||||
|
"./cypress/tsconfig.json"
|
||||||
|
],
|
||||||
|
"createDefaultProgram": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"prefer-const": "off",
|
||||||
|
|
||||||
|
// Custom DSpace Angular rules
|
||||||
|
"dspace-angular-ts/themed-component-usages": "error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -221,9 +291,9 @@
|
|||||||
"plugin:@angular-eslint/template/recommended"
|
"plugin:@angular-eslint/template/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
// todo: re-enable & fix errors
|
// Custom DSpace Angular rules
|
||||||
"@angular-eslint/template/no-negated-async": "off",
|
"dspace-angular-html/themed-component-usages": "error",
|
||||||
"@angular-eslint/template/eqeqeq": "off"
|
"dspace-angular-html/no-disabled-attribute-on-button": "error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -231,10 +301,13 @@
|
|||||||
"*.json5"
|
"*.json5"
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:jsonc/recommended-with-jsonc"
|
"plugin:jsonc/recommended-with-json5"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-irregular-whitespace": "error",
|
// The ESLint core no-irregular-whitespace rule doesn't work well in JSON
|
||||||
|
// See: https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-irregular-whitespace.html
|
||||||
|
"no-irregular-whitespace": "off",
|
||||||
|
"jsonc/no-irregular-whitespace": "error",
|
||||||
"no-trailing-spaces": "error",
|
"no-trailing-spaces": "error",
|
||||||
"jsonc/comma-dangle": [
|
"jsonc/comma-dangle": [
|
||||||
"error",
|
"error",
|
||||||
|
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -14,3 +14,6 @@
|
|||||||
*.scss eol=lf
|
*.scss eol=lf
|
||||||
*.html eol=lf
|
*.html eol=lf
|
||||||
*.svg eol=lf
|
*.svg eol=lf
|
||||||
|
|
||||||
|
# Generated documentation should have LF line endings to reduce git noise
|
||||||
|
docs/lint/**/*.md eol=lf
|
@@ -1,26 +0,0 @@
|
|||||||
# This workflow runs whenever a new pull request is created
|
|
||||||
# TEMPORARILY DISABLED. Unfortunately this doesn't work for PRs created from forked repositories (which is how we tend to create PRs).
|
|
||||||
# There is no known workaround yet. See https://github.community/t/how-to-use-github-token-for-prs-from-forks/16818
|
|
||||||
name: Pull Request opened
|
|
||||||
|
|
||||||
# Only run for newly opened PRs against the "main" branch
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
automation:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
# Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
|
|
||||||
# See https://github.com/marketplace/actions/pull-request-assigner
|
|
||||||
- name: Assign PR to creator
|
|
||||||
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
|
|
||||||
# Note, this authentication token is created automatically
|
|
||||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# Ignore errors. It is possible the PR was created by someone who cannot be assigned
|
|
||||||
continue-on-error: true
|
|
172
.github/workflows/build.yml
vendored
172
.github/workflows/build.yml
vendored
@@ -8,6 +8,7 @@ on: [push, pull_request]
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
packages: read # to fetch private images from GitHub Container Registry (GHCR)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
@@ -33,21 +34,26 @@ jobs:
|
|||||||
#CHROME_VERSION: "90.0.4430.212-1"
|
#CHROME_VERSION: "90.0.4430.212-1"
|
||||||
# Bump Node heap size (OOM in CI after upgrading to Angular 15)
|
# Bump Node heap size (OOM in CI after upgrading to Angular 15)
|
||||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||||
|
# Project name to use when running "docker compose" prior to e2e tests
|
||||||
|
COMPOSE_PROJECT_NAME: 'ci'
|
||||||
|
# Docker Registry to use for Docker compose scripts below.
|
||||||
|
# We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub.
|
||||||
|
DOCKER_REGISTRY: ghcr.io
|
||||||
strategy:
|
strategy:
|
||||||
# Create a matrix of Node versions to test against (in parallel)
|
# Create a matrix of Node versions to test against (in parallel)
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [16.x, 18.x]
|
node-version: [18.x, 20.x]
|
||||||
# Do NOT exit immediately if one matrix job fails
|
# Do NOT exit immediately if one matrix job fails
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
# These are the actual CI steps to perform per job
|
# These are the actual CI steps to perform per job
|
||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# https://github.com/actions/setup-node
|
# https://github.com/actions/setup-node
|
||||||
- name: Install Node.js ${{ matrix.node-version }}
|
- name: Install Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
@@ -72,7 +78,7 @@ jobs:
|
|||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||||
- name: Cache Yarn dependencies
|
- name: Cache Yarn dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
# Cache entire Yarn cache directory (see previous step)
|
# Cache entire Yarn cache directory (see previous step)
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
@@ -83,8 +89,14 @@ jobs:
|
|||||||
- name: Install Yarn dependencies
|
- name: Install Yarn dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build lint plugins
|
||||||
|
run: yarn run build:lint
|
||||||
|
|
||||||
|
- name: Run lint plugin tests
|
||||||
|
run: yarn run test:lint:nobuild
|
||||||
|
|
||||||
- name: Run lint
|
- name: Run lint
|
||||||
run: yarn run lint --quiet
|
run: yarn run lint:nobuild --quiet
|
||||||
|
|
||||||
- name: Check for circular dependencies
|
- name: Check for circular dependencies
|
||||||
run: yarn run check-circ-deps
|
run: yarn run check-circ-deps
|
||||||
@@ -99,26 +111,34 @@ jobs:
|
|||||||
# so that it can be shared with the 'codecov' job (see below)
|
# 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
|
# 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
|
- name: Upload code coverage report to Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: matrix.node-version == '18.x'
|
if: matrix.node-version == '18.x'
|
||||||
with:
|
with:
|
||||||
name: dspace-angular coverage report
|
name: coverage-report-${{ matrix.node-version }}
|
||||||
path: 'coverage/dspace-angular/lcov.info'
|
path: 'coverage/dspace-angular/lcov.info'
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
|
||||||
# Using docker-compose start backend using CI configuration
|
# Login to our Docker registry, so that we can access private Docker images using "docker compose" below.
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Using "docker compose" start backend using CI configuration
|
||||||
# and load assetstore from a cached copy
|
# and load assetstore from a cached copy
|
||||||
- name: Start DSpace REST Backend via Docker (for e2e tests)
|
- name: Start DSpace REST Backend via Docker (for e2e tests)
|
||||||
run: |
|
run: |
|
||||||
docker-compose -f ./docker/docker-compose-ci.yml up -d
|
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/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
|
||||||
docker container ls
|
docker container ls
|
||||||
|
|
||||||
# Run integration tests via Cypress.io
|
# Run integration tests via Cypress.io
|
||||||
# https://github.com/cypress-io/github-action
|
# https://github.com/cypress-io/github-action
|
||||||
# (NOTE: to run these e2e tests locally, just use 'ng e2e')
|
# (NOTE: to run these e2e tests locally, just use 'ng e2e')
|
||||||
- name: Run e2e tests (integration tests)
|
- name: Run e2e tests (integration tests)
|
||||||
uses: cypress-io/github-action@v5
|
uses: cypress-io/github-action@v6
|
||||||
with:
|
with:
|
||||||
# Run tests in Chrome, headless mode (default)
|
# Run tests in Chrome, headless mode (default)
|
||||||
browser: chrome
|
browser: chrome
|
||||||
@@ -133,19 +153,19 @@ jobs:
|
|||||||
# Cypress always creates a video of all e2e tests (whether they succeeded or failed)
|
# Cypress always creates a video of all e2e tests (whether they succeeded or failed)
|
||||||
# Save those in an Artifact
|
# Save those in an Artifact
|
||||||
- name: Upload e2e test videos to Artifacts
|
- name: Upload e2e test videos to Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: e2e-test-videos
|
name: e2e-test-videos-${{ matrix.node-version }}
|
||||||
path: cypress/videos
|
path: cypress/videos
|
||||||
|
|
||||||
# If e2e tests fail, Cypress creates a screenshot of what happened
|
# If e2e tests fail, Cypress creates a screenshot of what happened
|
||||||
# Save those in an Artifact
|
# Save those in an Artifact
|
||||||
- name: Upload e2e test failure screenshots to Artifacts
|
- name: Upload e2e test failure screenshots to Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: e2e-test-screenshots
|
name: e2e-test-screenshots-${{ matrix.node-version }}
|
||||||
path: cypress/screenshots
|
path: cypress/screenshots
|
||||||
|
|
||||||
- name: Stop app (in case it stays up after e2e tests)
|
- name: Stop app (in case it stays up after e2e tests)
|
||||||
@@ -170,17 +190,120 @@ jobs:
|
|||||||
# Get homepage and verify that the <meta name="title"> tag includes "DSpace".
|
# Get homepage and verify that the <meta name="title"> tag includes "DSpace".
|
||||||
# If it does, then SSR is working, as this tag is created by our MetadataService.
|
# If it does, then SSR is working, as this tag is created by our MetadataService.
|
||||||
# This step also prints entire HTML of homepage for easier debugging if grep fails.
|
# This step also prints entire HTML of homepage for easier debugging if grep fails.
|
||||||
- name: Verify SSR (server-side rendering)
|
- name: Verify SSR (server-side rendering) on Homepage
|
||||||
run: |
|
run: |
|
||||||
result=$(wget -O- -q http://127.0.0.1:4000/home)
|
result=$(wget -O- -q http://127.0.0.1:4000/home)
|
||||||
echo "$result"
|
echo "$result"
|
||||||
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep DSpace
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep DSpace
|
||||||
|
|
||||||
|
# Get a specific community in our test data and verify that the "<h1>" tag includes "Publications" (the community name).
|
||||||
|
# If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Community page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/communities/0958c910-2037-42a9-81c7-dca80e3892b4)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<h1 [^>]*>[^><]*</h1>" | grep Publications
|
||||||
|
|
||||||
|
# Get a specific collection in our test data and verify that the "<h1>" tag includes "Articles" (the collection name).
|
||||||
|
# If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Collection page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/collections/282164f5-d325-4740-8dd1-fa4d6d3e7200)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<h1 [^>]*>[^><]*</h1>" | grep Articles
|
||||||
|
|
||||||
|
# Get a specific publication in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the title of this publication. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Publication page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/publication/6160810f-1e53-40db-81ef-f6621a727398)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "An Economic Model of Mortality Salience"
|
||||||
|
|
||||||
|
# Get a specific person in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the person. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Person page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/person/b1b2c768-bda1-448a-a073-fc541e8b24d9)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Simmons, Cameron"
|
||||||
|
|
||||||
|
# Get a specific project in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the project. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Project page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/project/46ccb608-a74c-4bf6-bc7a-e29cc7defea9)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "University Research Fellowship"
|
||||||
|
|
||||||
|
# Get a specific orgunit in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the orgunit. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on an OrgUnit page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/orgunit/9851674d-bd9a-467b-8d84-068deb568ccf)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Law and Development"
|
||||||
|
|
||||||
|
# Get a specific journal in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the journal. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Journal page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/journal/d4af6c3e-53d0-4757-81eb-566f3b45d63a)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental & Architectural Phenomenology"
|
||||||
|
|
||||||
|
# Get a specific journal volume in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the volume. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Journal Volume page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/journalvolume/07c6249f-4bf7-494d-9ce3-6ffdb2aed538)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental & Architectural Phenomenology Volume 28 (2017)"
|
||||||
|
|
||||||
|
# Get a specific journal issue in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the issue. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Journal Issue page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/journalissue/44c29473-5de2-48fa-b005-e5029aa1a50b)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental & Architectural Phenomenology Vol. 28, No. 1"
|
||||||
|
|
||||||
|
# Verify 301 Handle redirect behavior
|
||||||
|
# Note: /handle/123456789/260 is the same test Publication used by our e2e tests
|
||||||
|
- name: Verify 301 redirect from '/handle' URLs
|
||||||
|
run: |
|
||||||
|
result=$(wget --server-response --quiet http://127.0.0.1:4000/handle/123456789/260 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result"
|
||||||
|
[[ "$result" -eq "301" ]]
|
||||||
|
|
||||||
|
# Verify 403 error code behavior
|
||||||
|
- name: Verify 403 error code from '/403'
|
||||||
|
run: |
|
||||||
|
result=$(wget --server-response --quiet http://127.0.0.1:4000/403 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result"
|
||||||
|
[[ "$result" -eq "403" ]]
|
||||||
|
|
||||||
|
# Verify 404 error code behavior
|
||||||
|
- name: Verify 404 error code from '/404' and on invalid pages
|
||||||
|
run: |
|
||||||
|
result=$(wget --server-response --quiet http://127.0.0.1:4000/404 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result"
|
||||||
|
result2=$(wget --server-response --quiet http://127.0.0.1:4000/invalidurl 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result2"
|
||||||
|
[[ "$result" -eq "404" && "$result2" -eq "404" ]]
|
||||||
|
|
||||||
|
# Verify 500 error code behavior
|
||||||
|
- name: Verify 500 error code from '/500'
|
||||||
|
run: |
|
||||||
|
result=$(wget --server-response --quiet http://127.0.0.1:4000/500 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result"
|
||||||
|
[[ "$result" -eq "500" ]]
|
||||||
|
|
||||||
- name: Stop running app
|
- name: Stop running app
|
||||||
run: kill -9 $(lsof -t -i:4000)
|
run: kill -9 $(lsof -t -i:4000)
|
||||||
|
|
||||||
- name: Shutdown Docker containers
|
- 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
|
# 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.
|
# job above. This is necessary because Codecov uploads seem to randomly fail at times.
|
||||||
@@ -191,11 +314,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Download artifacts from previous 'tests' job
|
# Download artifacts from previous 'tests' job
|
||||||
- name: Download coverage artifacts
|
- name: Download coverage artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
# Now attempt upload to Codecov using its action.
|
# 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.
|
# NOTE: We use a retry action to retry the Codecov upload if it fails the first time.
|
||||||
@@ -203,10 +326,15 @@ jobs:
|
|||||||
# Retry action: https://github.com/marketplace/actions/retry-action
|
# Retry action: https://github.com/marketplace/actions/retry-action
|
||||||
# Codecov action: https://github.com/codecov/codecov-action
|
# Codecov action: https://github.com/codecov/codecov-action
|
||||||
- name: Upload coverage to Codecov.io
|
- name: Upload coverage to Codecov.io
|
||||||
uses: Wandalen/wretry.action@v1.0.36
|
uses: Wandalen/wretry.action@v1.3.0
|
||||||
with:
|
with:
|
||||||
action: codecov/codecov-action@v3
|
action: codecov/codecov-action@v4
|
||||||
# Try upload 5 times max
|
# 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
|
attempt_limit: 5
|
||||||
# Run again in 30 seconds
|
# Run again in 30 seconds
|
||||||
attempt_delay: 30000
|
attempt_delay: 30000
|
||||||
|
12
.github/workflows/codescan.yml
vendored
12
.github/workflows/codescan.yml
vendored
@@ -5,12 +5,16 @@
|
|||||||
# because CodeQL requires a fresh build with all tests *disabled*.
|
# because CodeQL requires a fresh build with all tests *disabled*.
|
||||||
name: "Code Scanning"
|
name: "Code Scanning"
|
||||||
|
|
||||||
# Run this code scan for all pushes / PRs to main branch. Also run once a week.
|
# Run this code scan for all pushes / PRs to main or maintenance branches. Also run once a week.
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches:
|
||||||
|
- main
|
||||||
|
- 'dspace-**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches:
|
||||||
|
- main
|
||||||
|
- 'dspace-**'
|
||||||
# Don't run if PR is only updating static documentation
|
# Don't run if PR is only updating static documentation
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
@@ -31,7 +35,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
# https://github.com/github/codeql-action
|
# https://github.com/github/codeql-action
|
||||||
|
128
.github/workflows/docker.yml
vendored
128
.github/workflows/docker.yml
vendored
@@ -3,6 +3,9 @@ name: Docker images
|
|||||||
|
|
||||||
# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases.
|
# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases.
|
||||||
# Also run for PRs to ensure PR doesn't break Docker build process
|
# Also run for PRs to ensure PR doesn't break Docker build process
|
||||||
|
# NOTE: uses "reusable-docker-build.yml" in DSpace/DSpace to actually build each of the Docker images
|
||||||
|
# https://github.com/DSpace/DSpace/blob/main/.github/workflows/reusable-docker-build.yml
|
||||||
|
#
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -14,107 +17,44 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
packages: write # to write images to GitHub Container Registry (GHCR)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
#############################################################
|
||||||
|
# Build/Push the 'dspace/dspace-angular' image
|
||||||
|
#############################################################
|
||||||
|
dspace-angular:
|
||||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
|
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
|
||||||
if: github.repository == 'dspace/dspace-angular'
|
if: github.repository == 'dspace/dspace-angular'
|
||||||
runs-on: ubuntu-latest
|
# Use the reusable-docker-build.yml script from DSpace/DSpace repo to build our Docker image
|
||||||
env:
|
uses: DSpace/DSpace/.github/workflows/reusable-docker-build.yml@main
|
||||||
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
|
|
||||||
# For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image.
|
|
||||||
# For a new commit on other branches, use the branch name as the tag for Docker image.
|
|
||||||
# For a new tag, copy that tag name as the tag for Docker image.
|
|
||||||
IMAGE_TAGS: |
|
|
||||||
type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
|
||||||
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
|
|
||||||
type=ref,event=tag
|
|
||||||
# Define default tag "flavor" for docker/metadata-action per
|
|
||||||
# https://github.com/docker/metadata-action#flavor-input
|
|
||||||
# We turn off 'latest' tag by default.
|
|
||||||
TAGS_FLAVOR: |
|
|
||||||
latest=false
|
|
||||||
# Architectures / Platforms for which we will build Docker images
|
|
||||||
# If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work.
|
|
||||||
# If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64.
|
|
||||||
PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# https://github.com/actions/checkout
|
|
||||||
- name: Checkout codebase
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
|
||||||
- name: Setup Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
# https://github.com/docker/setup-qemu-action
|
|
||||||
- name: Set up QEMU emulation to build for multiple architectures
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
|
|
||||||
# https://github.com/docker/login-action
|
|
||||||
- name: Login to DockerHub
|
|
||||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
build_id: dspace-angular-dev
|
||||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
image_name: dspace/dspace-angular
|
||||||
|
dockerfile_path: ./Dockerfile
|
||||||
|
secrets:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
|
||||||
###############################################
|
#############################################################
|
||||||
# Build/Push the 'dspace/dspace-angular' image
|
|
||||||
###############################################
|
|
||||||
# https://github.com/docker/metadata-action
|
|
||||||
# Get Metadata for docker_build step below
|
|
||||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image
|
|
||||||
id: meta_build
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
with:
|
|
||||||
images: dspace/dspace-angular
|
|
||||||
tags: ${{ env.IMAGE_TAGS }}
|
|
||||||
flavor: ${{ env.TAGS_FLAVOR }}
|
|
||||||
|
|
||||||
# https://github.com/docker/build-push-action
|
|
||||||
- name: Build and push 'dspace-angular' image
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: ${{ env.PLATFORMS }}
|
|
||||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
|
||||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
# Use tags / labels provided by 'docker/metadata-action' above
|
|
||||||
tags: ${{ steps.meta_build.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta_build.outputs.labels }}
|
|
||||||
|
|
||||||
#####################################################
|
|
||||||
# Build/Push the 'dspace/dspace-angular' image ('-dist' tag)
|
# Build/Push the 'dspace/dspace-angular' image ('-dist' tag)
|
||||||
#####################################################
|
#############################################################
|
||||||
# https://github.com/docker/metadata-action
|
dspace-angular-dist:
|
||||||
# Get Metadata for docker_build_dist step below
|
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
|
||||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular-dist' image
|
if: github.repository == 'dspace/dspace-angular'
|
||||||
id: meta_build_dist
|
# Use the reusable-docker-build.yml script from DSpace/DSpace repo to build our Docker image
|
||||||
uses: docker/metadata-action@v4
|
uses: DSpace/DSpace/.github/workflows/reusable-docker-build.yml@main
|
||||||
with:
|
with:
|
||||||
images: dspace/dspace-angular
|
build_id: dspace-angular-dist
|
||||||
tags: ${{ env.IMAGE_TAGS }}
|
image_name: dspace/dspace-angular
|
||||||
|
dockerfile_path: ./Dockerfile.dist
|
||||||
# As this is a "dist" image, its tags are all suffixed with "-dist". Otherwise, it uses the same
|
# As this is a "dist" image, its tags are all suffixed with "-dist". Otherwise, it uses the same
|
||||||
# tagging logic as the primary 'dspace/dspace-angular' image above.
|
# tagging logic as the primary 'dspace/dspace-angular' image above.
|
||||||
flavor: ${{ env.TAGS_FLAVOR }}
|
tags_flavor: suffix=-dist
|
||||||
suffix=-dist
|
secrets:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
- name: Build and push 'dspace-angular-dist' image
|
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
id: docker_build_dist
|
# Enable redeploy of sandbox & demo if the branch for this image matches the deployment branch of
|
||||||
uses: docker/build-push-action@v3
|
# these sites as specified in reusable-docker-build.xml
|
||||||
with:
|
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
|
||||||
context: .
|
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
|
||||||
file: ./Dockerfile.dist
|
|
||||||
platforms: ${{ env.PLATFORMS }}
|
|
||||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
|
||||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
# Use tags / labels provided by 'docker/metadata-action' above
|
|
||||||
tags: ${{ steps.meta_build_dist.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta_build_dist.outputs.labels }}
|
|
2
.github/workflows/issue_opened.yml
vendored
2
.github/workflows/issue_opened.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
# Only add to project board if issue is flagged as "needs triage" or has no labels
|
# 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
|
# 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) == '')
|
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.
|
# 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
|
# 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
|
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
|
||||||
|
9
.github/workflows/label_merge_conflicts.yml
vendored
9
.github/workflows/label_merge_conflicts.yml
vendored
@@ -1,11 +1,12 @@
|
|||||||
# This workflow checks open PRs for merge conflicts and labels them when conflicts are found
|
# This workflow checks open PRs for merge conflicts and labels them when conflicts are found
|
||||||
name: Check for merge conflicts
|
name: Check for merge conflicts
|
||||||
|
|
||||||
# Run whenever the "main" branch is updated
|
# Run this for all pushes (i.e. merges) to 'main' or maintenance branches
|
||||||
# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches:
|
||||||
|
- main
|
||||||
|
- 'dspace-**'
|
||||||
# So that the `conflict_label_name` is removed if conflicts are resolved,
|
# So that the `conflict_label_name` is removed if conflicts are resolved,
|
||||||
# we allow this to run for `pull_request_target` so that github secrets are available.
|
# we allow this to run for `pull_request_target` so that github secrets are available.
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
@@ -24,6 +25,8 @@ jobs:
|
|||||||
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
|
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
|
||||||
- name: Auto-label PRs with merge conflicts
|
- name: Auto-label PRs with merge conflicts
|
||||||
uses: prince-chrismc/label-merge-conflicts-action@v3
|
uses: prince-chrismc/label-merge-conflicts-action@v3
|
||||||
|
# Ignore any failures -- may occur (randomly?) for older, outdated PRs.
|
||||||
|
continue-on-error: true
|
||||||
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
|
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
|
||||||
# Note, the authentication token is created automatically
|
# Note, the authentication token is created automatically
|
||||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
|
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
|
||||||
|
46
.github/workflows/port_merged_pull_request.yml
vendored
Normal file
46
.github/workflows/port_merged_pull_request.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# This workflow will attempt to port a merged pull request to
|
||||||
|
# the branch specified in a "port to" label (if exists)
|
||||||
|
name: Port merged Pull Request
|
||||||
|
|
||||||
|
# Only run for merged PRs against the "main" or maintenance branches
|
||||||
|
# We allow this to run for `pull_request_target` so that github secrets are available
|
||||||
|
# (This is required when the PR comes from a forked repo)
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [ closed ]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'dspace-**'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # so action can add comments
|
||||||
|
pull-requests: write # so action can create pull requests
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
port_pr:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Don't run on closed *unmerged* pull requests
|
||||||
|
if: github.event.pull_request.merged
|
||||||
|
steps:
|
||||||
|
# Checkout code
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
# Port PR to other branch (ONLY if labeled with "port to")
|
||||||
|
# See https://github.com/korthout/backport-action
|
||||||
|
- name: Create backport pull requests
|
||||||
|
uses: korthout/backport-action@v2
|
||||||
|
with:
|
||||||
|
# Trigger based on a "port to [branch]" label on PR
|
||||||
|
# (This label must specify the branch name to port to)
|
||||||
|
label_pattern: '^port to ([^ ]+)$'
|
||||||
|
# Title to add to the (newly created) port PR
|
||||||
|
pull_title: '[Port ${target_branch}] ${pull_title}'
|
||||||
|
# Description to add to the (newly created) port PR
|
||||||
|
pull_description: 'Port of #${pull_number} by @${pull_author} to `${target_branch}`.'
|
||||||
|
# Copy all labels from original PR to (newly created) port PR
|
||||||
|
# NOTE: The labels matching 'label_pattern' are automatically excluded
|
||||||
|
copy_labels_pattern: '.*'
|
||||||
|
# Skip any merge commits in the ported PR. This means only non-merge commits are cherry-picked to the new PR
|
||||||
|
merge_commits: 'skip'
|
||||||
|
# Use a personal access token (PAT) to create PR as 'dspace-bot' user.
|
||||||
|
# A PAT is required in order for the new PR to trigger its own actions (for CI checks)
|
||||||
|
github_token: ${{ secrets.PR_PORT_TOKEN }}
|
24
.github/workflows/pull_request_opened.yml
vendored
Normal file
24
.github/workflows/pull_request_opened.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# This workflow runs whenever a new pull request is created
|
||||||
|
name: Pull Request opened
|
||||||
|
|
||||||
|
# Only run for newly opened PRs against the "main" or maintenance branches
|
||||||
|
# We allow this to run for `pull_request_target` so that github secrets are available
|
||||||
|
# (This is required to assign a PR back to the creator when the PR comes from a forked repo)
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [ opened ]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'dspace-**'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
automation:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# 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.1.0
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
/.angular/cache
|
/.angular/cache
|
||||||
|
/.nx
|
||||||
/__build__
|
/__build__
|
||||||
/__server_build__
|
/__server_build__
|
||||||
/node_modules
|
/node_modules
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# This image will be published as dspace/dspace-angular
|
# This image will be published as dspace/dspace-angular
|
||||||
# See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details
|
# See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details
|
||||||
|
|
||||||
FROM node:18-alpine
|
FROM docker.io/node:18-alpine
|
||||||
|
|
||||||
# Ensure Python and other build tools are available
|
# Ensure Python and other build tools are available
|
||||||
# These are needed to install some node modules, especially on linux/arm64
|
# These are needed to install some node modules, especially on linux/arm64
|
||||||
@@ -24,5 +24,5 @@ ENV NODE_OPTIONS="--max_old_space_size=4096"
|
|||||||
# Listen / accept connections from all IP addresses.
|
# Listen / accept connections from all IP addresses.
|
||||||
# NOTE: At this time it is only possible to run Docker container in Production mode
|
# NOTE: At this time it is only possible to run Docker container in Production mode
|
||||||
# if you have a public URL. See https://github.com/DSpace/dspace-angular/issues/1485
|
# if you have a public URL. See https://github.com/DSpace/dspace-angular/issues/1485
|
||||||
ENV NODE_ENV development
|
ENV NODE_ENV=development
|
||||||
CMD yarn serve --host 0.0.0.0
|
CMD yarn serve --host 0.0.0.0
|
||||||
|
@@ -2,9 +2,9 @@
|
|||||||
# See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details
|
# See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details
|
||||||
|
|
||||||
# Test build:
|
# Test build:
|
||||||
# docker build -f Dockerfile.dist -t dspace/dspace-angular:dspace-7_x-dist .
|
# docker build -f Dockerfile.dist -t dspace/dspace-angular:dspace-8_x-dist .
|
||||||
|
|
||||||
FROM node:18-alpine as build
|
FROM docker.io/node:18-alpine AS build
|
||||||
|
|
||||||
# Ensure Python and other build tools are available
|
# Ensure Python and other build tools are available
|
||||||
# These are needed to install some node modules, especially on linux/arm64
|
# These are needed to install some node modules, especially on linux/arm64
|
||||||
@@ -26,6 +26,6 @@ COPY --chown=node:node docker/dspace-ui.json /app/dspace-ui.json
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
USER node
|
USER node
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV=production
|
||||||
EXPOSE 4000
|
EXPOSE 4000
|
||||||
CMD pm2-runtime start dspace-ui.json --json
|
CMD pm2-runtime start dspace-ui.json --json
|
||||||
|
10
README.md
10
README.md
@@ -35,7 +35,7 @@ https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace
|
|||||||
Quick start
|
Quick start
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
**Ensure you're running [Node](https://nodejs.org) `v16.x` or `v18.x`, [npm](https://www.npmjs.com/) >= `v5.x` and [yarn](https://yarnpkg.com) == `v1.x`**
|
**Ensure you're running [Node](https://nodejs.org) `v18.x` or `v20.x`, [npm](https://www.npmjs.com/) >= `v10.x` and [yarn](https://yarnpkg.com) == `v1.x`**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# clone the repo
|
# clone the repo
|
||||||
@@ -90,7 +90,7 @@ Requirements
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org) and [yarn](https://yarnpkg.com)
|
- [Node.js](https://nodejs.org) and [yarn](https://yarnpkg.com)
|
||||||
- Ensure you're running node `v16.x` or `v18.x` and yarn == `v1.x`
|
- Ensure you're running node `v18.x` or `v20.x` and yarn == `v1.x`
|
||||||
|
|
||||||
If you have [`nvm`](https://github.com/creationix/nvm#install-script) or [`nvm-windows`](https://github.com/coreybutler/nvm-windows) installed, which is highly recommended, you can run `nvm install --lts && nvm use` to install and start using the latest Node LTS.
|
If you have [`nvm`](https://github.com/creationix/nvm#install-script) or [`nvm-windows`](https://github.com/coreybutler/nvm-windows) installed, which is highly recommended, you can run `nvm install --lts && nvm use` to install and start using the latest Node LTS.
|
||||||
|
|
||||||
@@ -157,8 +157,8 @@ DSPACE_UI_SSL => DSPACE_SSL
|
|||||||
|
|
||||||
The same settings can also be overwritten by setting system environment variables instead, E.g.:
|
The same settings can also be overwritten by setting system environment variables instead, E.g.:
|
||||||
```bash
|
```bash
|
||||||
export DSPACE_HOST=api7.dspace.org
|
export DSPACE_HOST=demo.dspace.org
|
||||||
export DSPACE_UI_PORT=4200
|
export DSPACE_UI_PORT=4000
|
||||||
```
|
```
|
||||||
|
|
||||||
The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `DSPACE_APP_CONFIG_PATH` overrides **`config.(prod or dev).yml`**
|
The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `DSPACE_APP_CONFIG_PATH` overrides **`config.(prod or dev).yml`**
|
||||||
@@ -288,7 +288,7 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con
|
|||||||
The test files can be found in the `./cypress/integration/` folder.
|
The test files can be found in the `./cypress/integration/` folder.
|
||||||
|
|
||||||
Before you can run e2e tests, two things are REQUIRED:
|
Before you can run e2e tests, two things are REQUIRED:
|
||||||
1. You MUST be running the DSpace backend (i.e. REST API) locally. The e2e tests will *NOT* succeed if run against our demo REST API (https://api7.dspace.org/server/), as that server is uncontrolled and may have content added/removed at any time.
|
1. You MUST be running the DSpace backend (i.e. REST API) locally. The e2e tests will *NOT* succeed if run against our demo/sandbox REST API (https://demo.dspace.org/server/ or https://sandbox.dspace.org/server/), as those sites may have content added/removed at any time.
|
||||||
* After starting up your backend on localhost, make sure either your `config.prod.yml` or `config.dev.yml` has its `rest` settings defined to use that localhost backend.
|
* After starting up your backend on localhost, make sure either your `config.prod.yml` or `config.dev.yml` has its `rest` settings defined to use that localhost backend.
|
||||||
* If you'd prefer, you may instead use environment variables as described at [Configuring](#configuring). For example:
|
* If you'd prefer, you may instead use environment variables as described at [Configuring](#configuring). For example:
|
||||||
```
|
```
|
||||||
|
21
angular.json
21
angular.json
@@ -30,7 +30,6 @@
|
|||||||
"lodash",
|
"lodash",
|
||||||
"jwt-decode",
|
"jwt-decode",
|
||||||
"uuid",
|
"uuid",
|
||||||
"webfontloader",
|
|
||||||
"zone.js"
|
"zone.js"
|
||||||
],
|
],
|
||||||
"outputPath": "dist/browser",
|
"outputPath": "dist/browser",
|
||||||
@@ -109,22 +108,22 @@
|
|||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular-builders/custom-webpack:dev-server",
|
"builder": "@angular-builders/custom-webpack:dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "dspace-angular:build",
|
"buildTarget": "dspace-angular:build",
|
||||||
"port": 4000
|
"port": 4000
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"development": {
|
"development": {
|
||||||
"browserTarget": "dspace-angular:build:development"
|
"buildTarget": "dspace-angular:build:development"
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "dspace-angular:build:production"
|
"buildTarget": "dspace-angular:build:production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "dspace-angular:build"
|
"buildTarget": "dspace-angular:build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
@@ -217,23 +216,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serve-ssr": {
|
"serve-ssr": {
|
||||||
"builder": "@nguniversal/builders:ssr-dev-server",
|
"builder": "@angular-devkit/build-angular:ssr-dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "dspace-angular:build",
|
"buildTarget": "dspace-angular:build",
|
||||||
"serverTarget": "dspace-angular:server",
|
"serverTarget": "dspace-angular:server",
|
||||||
"port": 4000
|
"port": 4000
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "dspace-angular:build:production",
|
"buildTarget": "dspace-angular:build:production",
|
||||||
"serverTarget": "dspace-angular:server:production"
|
"serverTarget": "dspace-angular:server:production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prerender": {
|
"prerender": {
|
||||||
"builder": "@nguniversal/builders:prerender",
|
"builder": "@angular-devkit/build-angular:prerender",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "dspace-angular:build:production",
|
"buildTarget": "dspace-angular:build:production",
|
||||||
"serverTarget": "dspace-angular:server:production",
|
"serverTarget": "dspace-angular:server:production",
|
||||||
"routes": [
|
"routes": [
|
||||||
"/"
|
"/"
|
||||||
@@ -266,6 +265,8 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"lintFilePatterns": [
|
"lintFilePatterns": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
|
"cypress/**/*.ts",
|
||||||
|
"lint/**/*.ts",
|
||||||
"src/**/*.html",
|
"src/**/*.html",
|
||||||
"src/**/*.json5"
|
"src/**/*.json5"
|
||||||
]
|
]
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# NOTE: will log all redux actions and transfers in console
|
# NOTE: will log all redux actions and transfers in console
|
||||||
debug: false
|
debug: false
|
||||||
|
|
||||||
# Angular Universal server settings
|
# Angular User Inteface settings
|
||||||
# NOTE: these settings define where Node.js will start your UI application. Therefore, these
|
# NOTE: these settings define where Node.js will start your UI application. Therefore, these
|
||||||
# "ui" settings usually specify a localhost port/URL which is later proxied to a public URL (using Apache or similar)
|
# "ui" settings usually specify a localhost port/URL which is later proxied to a public URL (using Apache or similar)
|
||||||
ui:
|
ui:
|
||||||
@@ -17,15 +17,64 @@ ui:
|
|||||||
# Trust X-FORWARDED-* headers from proxies (default = true)
|
# Trust X-FORWARDED-* headers from proxies (default = true)
|
||||||
useProxies: true
|
useProxies: true
|
||||||
|
|
||||||
|
# Angular Server Side Rendering (SSR) settings
|
||||||
|
ssr:
|
||||||
|
# Whether to tell Angular to inline "critical" styles into the server-side rendered HTML.
|
||||||
|
# Determining which styles are critical is a relatively expensive operation; this option is
|
||||||
|
# disabled (false) by default to boost server performance at the expense of loading smoothness.
|
||||||
|
inlineCriticalCss: false
|
||||||
|
# Patterns to be run as regexes against the path of the page to check if SSR is allowed.
|
||||||
|
# If the path match any of the regexes it will be served directly in CSR.
|
||||||
|
# By default, excludes community and collection browse, global browse, global search, community list, statistics and various administrative tools.
|
||||||
|
excludePathPatterns:
|
||||||
|
- pattern: "^/communities/[a-f0-9-]{36}/browse(/.*)?$"
|
||||||
|
flag: "i"
|
||||||
|
- pattern: "^/collections/[a-f0-9-]{36}/browse(/.*)?$"
|
||||||
|
flag: "i"
|
||||||
|
- pattern: "^/browse/"
|
||||||
|
- pattern: "^/search$"
|
||||||
|
- pattern: "^/community-list$"
|
||||||
|
- pattern: "^/admin/"
|
||||||
|
- pattern: "^/processes/?"
|
||||||
|
- pattern: "^/notifications/"
|
||||||
|
- pattern: "^/statistics/?"
|
||||||
|
- pattern: "^/access-control/"
|
||||||
|
- pattern: "^/health$"
|
||||||
|
|
||||||
|
# Whether to enable rendering of Search component on SSR.
|
||||||
|
# If set to true the component will be included in the HTML returned from the server side rendering.
|
||||||
|
# If set to false the component will not be included in the HTML returned from the server side rendering.
|
||||||
|
enableSearchComponent: false
|
||||||
|
# Whether to enable rendering of Browse component on SSR.
|
||||||
|
# If set to true the component will be included in the HTML returned from the server side rendering.
|
||||||
|
# If set to false the component will not be included in the HTML returned from the server side rendering.
|
||||||
|
enableBrowseComponent: false
|
||||||
|
# Enable state transfer from the server-side application to the client-side application.
|
||||||
|
# Defaults to true.
|
||||||
|
# Note: When using an external application cache layer, it's recommended not to transfer the state to avoid caching it.
|
||||||
|
# Disabling it ensures that dynamic state information is not inadvertently cached, which can improve security and
|
||||||
|
# ensure that users always use the most up-to-date state.
|
||||||
|
transferState: true
|
||||||
|
# When a different REST base URL is used for the server-side application, the generated state contains references to
|
||||||
|
# REST resources with the internal URL configured. By default, these internal URLs are replaced with public URLs.
|
||||||
|
# Disable this setting to avoid URL replacement during SSR. In this the state is not transferred to avoid security issues.
|
||||||
|
replaceRestUrl: true
|
||||||
|
# Enable request performance profiling data collection and printing the results in the server console.
|
||||||
|
# Defaults to false. Enabling in production is NOT recommended
|
||||||
|
#enablePerformanceProfiler: false
|
||||||
|
|
||||||
# The REST API server settings
|
# The REST API server settings
|
||||||
# NOTE: these settings define which (publicly available) REST API to use. They are usually
|
# 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.
|
# 'synced' with the 'dspace.server.url' setting in your backend's local.cfg.
|
||||||
rest:
|
rest:
|
||||||
ssl: true
|
ssl: true
|
||||||
host: api7.dspace.org
|
host: sandbox.dspace.org
|
||||||
port: 443
|
port: 443
|
||||||
# NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
# NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||||
nameSpace: /server
|
nameSpace: /server
|
||||||
|
# Provide a different REST url to be used during SSR execution. It must contain the whole url including protocol, server port and
|
||||||
|
# server namespace (uncomment to use it).
|
||||||
|
#ssrBaseUrl: http://localhost:8080/server
|
||||||
|
|
||||||
# Caching settings
|
# Caching settings
|
||||||
cache:
|
cache:
|
||||||
@@ -131,12 +180,16 @@ submission:
|
|||||||
# NOTE: after how many time (milliseconds) submission is saved automatically
|
# NOTE: after how many time (milliseconds) submission is saved automatically
|
||||||
# eg. timer: 5 * (1000 * 60); // 5 minutes
|
# eg. timer: 5 * (1000 * 60); // 5 minutes
|
||||||
timer: 0
|
timer: 0
|
||||||
|
# Always show the duplicate detection section if enabled, even if there are no potential duplicates detected
|
||||||
|
# (a message will be displayed to indicate no matches were found)
|
||||||
|
duplicateDetection:
|
||||||
|
alwaysShowSection: false
|
||||||
icons:
|
icons:
|
||||||
metadata:
|
metadata:
|
||||||
# NOTE: example of configuration
|
# NOTE: example of configuration
|
||||||
# # NOTE: metadata name
|
# # NOTE: metadata name
|
||||||
# - name: dc.author
|
# - name: dc.author
|
||||||
# # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used
|
# # NOTE: fontawesome (v6.x) icon classes and bootstrap utility classes can be used
|
||||||
# style: fas fa-user
|
# style: fas fa-user
|
||||||
- name: dc.author
|
- name: dc.author
|
||||||
style: fas fa-user
|
style: fas fa-user
|
||||||
@@ -147,18 +200,40 @@ submission:
|
|||||||
confidence:
|
confidence:
|
||||||
# NOTE: example of configuration
|
# NOTE: example of configuration
|
||||||
# # NOTE: confidence value
|
# # NOTE: confidence value
|
||||||
# - name: dc.author
|
# - value: 600
|
||||||
# # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used
|
# # NOTE: fontawesome (v6.x) icon classes and bootstrap utility classes can be used
|
||||||
# style: fa-user
|
# style: text-success
|
||||||
|
# icon: fa-circle-check
|
||||||
|
# # NOTE: the class configured in property style is used by default, the icon property could be used in component
|
||||||
|
# configured to use a 'icon mode' display (mainly in edit-item page)
|
||||||
- value: 600
|
- value: 600
|
||||||
style: text-success
|
style: text-success
|
||||||
|
icon: fa-circle-check
|
||||||
- value: 500
|
- value: 500
|
||||||
style: text-info
|
style: text-info
|
||||||
|
icon: fa-gear
|
||||||
- value: 400
|
- value: 400
|
||||||
style: text-warning
|
style: text-warning
|
||||||
|
icon: fa-circle-question
|
||||||
|
- value: 300
|
||||||
|
style: text-muted
|
||||||
|
icon: fa-thumbs-down
|
||||||
|
- value: 200
|
||||||
|
style: text-muted
|
||||||
|
icon: fa-circle-exclamation
|
||||||
|
- value: 100
|
||||||
|
style: text-muted
|
||||||
|
icon: fa-circle-stop
|
||||||
|
- value: 0
|
||||||
|
style: text-muted
|
||||||
|
icon: fa-ban
|
||||||
|
- value: -1
|
||||||
|
style: text-muted
|
||||||
|
icon: fa-circle-xmark
|
||||||
# default configuration
|
# default configuration
|
||||||
- value: default
|
- value: default
|
||||||
style: text-muted
|
style: text-muted
|
||||||
|
icon: fa-circle-xmark
|
||||||
|
|
||||||
# Default Language in which the UI will be rendered if the user's browser language is not an active language
|
# Default Language in which the UI will be rendered if the user's browser language is not an active language
|
||||||
defaultLanguage: en
|
defaultLanguage: en
|
||||||
@@ -169,6 +244,12 @@ languages:
|
|||||||
- code: en
|
- code: en
|
||||||
label: English
|
label: English
|
||||||
active: true
|
active: true
|
||||||
|
- code: ar
|
||||||
|
label: العربية
|
||||||
|
active: true
|
||||||
|
- code: bn
|
||||||
|
label: বাংলা
|
||||||
|
active: true
|
||||||
- code: ca
|
- code: ca
|
||||||
label: Català
|
label: Català
|
||||||
active: true
|
active: true
|
||||||
@@ -178,24 +259,36 @@ languages:
|
|||||||
- code: de
|
- code: de
|
||||||
label: Deutsch
|
label: Deutsch
|
||||||
active: true
|
active: true
|
||||||
|
- code: el
|
||||||
|
label: Ελληνικά
|
||||||
|
active: true
|
||||||
- code: es
|
- code: es
|
||||||
label: Español
|
label: Español
|
||||||
active: true
|
active: true
|
||||||
|
- code: fi
|
||||||
|
label: Suomi
|
||||||
|
active: true
|
||||||
- code: fr
|
- code: fr
|
||||||
label: Français
|
label: Français
|
||||||
active: true
|
active: true
|
||||||
- code: gd
|
- code: gd
|
||||||
label: Gàidhlig
|
label: Gàidhlig
|
||||||
active: true
|
active: true
|
||||||
- code: it
|
- code: hi
|
||||||
label: Italiano
|
label: हिंदी
|
||||||
active: true
|
|
||||||
- code: lv
|
|
||||||
label: Latviešu
|
|
||||||
active: true
|
active: true
|
||||||
- code: hu
|
- code: hu
|
||||||
label: Magyar
|
label: Magyar
|
||||||
active: true
|
active: true
|
||||||
|
- code: it
|
||||||
|
label: Italiano
|
||||||
|
active: true
|
||||||
|
- code: kk
|
||||||
|
label: Қазақ
|
||||||
|
active: true
|
||||||
|
- code: lv
|
||||||
|
label: Latviešu
|
||||||
|
active: true
|
||||||
- code: nl
|
- code: nl
|
||||||
label: Nederlands
|
label: Nederlands
|
||||||
active: true
|
active: true
|
||||||
@@ -208,8 +301,11 @@ languages:
|
|||||||
- code: pt-BR
|
- code: pt-BR
|
||||||
label: Português do Brasil
|
label: Português do Brasil
|
||||||
active: true
|
active: true
|
||||||
- code: fi
|
- code: sr-lat
|
||||||
label: Suomi
|
label: Srpski (lat)
|
||||||
|
active: true
|
||||||
|
- code: sr-cyr
|
||||||
|
label: Српски
|
||||||
active: true
|
active: true
|
||||||
- code: sv
|
- code: sv
|
||||||
label: Svenska
|
label: Svenska
|
||||||
@@ -217,24 +313,12 @@ languages:
|
|||||||
- code: tr
|
- code: tr
|
||||||
label: Türkçe
|
label: Türkçe
|
||||||
active: true
|
active: true
|
||||||
- code: vi
|
|
||||||
label: Tiếng Việt
|
|
||||||
active: true
|
|
||||||
- code: kk
|
|
||||||
label: Қазақ
|
|
||||||
active: true
|
|
||||||
- code: bn
|
|
||||||
label: বাংলা
|
|
||||||
active: true
|
|
||||||
- code: hi
|
|
||||||
label: हिंदी
|
|
||||||
active: true
|
|
||||||
- code: el
|
|
||||||
label: Ελληνικά
|
|
||||||
active: true
|
|
||||||
- code: uk
|
- code: uk
|
||||||
label: Yкраї́нська
|
label: Yкраї́нська
|
||||||
active: true
|
active: true
|
||||||
|
- code: vi
|
||||||
|
label: Tiếng Việt
|
||||||
|
active: true
|
||||||
|
|
||||||
|
|
||||||
# Browse-By Pages
|
# Browse-By Pages
|
||||||
@@ -266,6 +350,8 @@ homePage:
|
|||||||
# No. of communities to list per page on the home page
|
# No. of communities to list per page on the home page
|
||||||
# This will always round to the nearest number from the list of page sizes. e.g. if you set it to 7 it'll use 10
|
# This will always round to the nearest number from the list of page sizes. e.g. if you set it to 7 it'll use 10
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
|
# Enable or disable the Discover filters on the homepage
|
||||||
|
showDiscoverFilters: false
|
||||||
|
|
||||||
# Item Config
|
# Item Config
|
||||||
item:
|
item:
|
||||||
@@ -279,8 +365,17 @@ item:
|
|||||||
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
|
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
|
|
||||||
|
# Community Page Config
|
||||||
|
community:
|
||||||
|
# Search tab config
|
||||||
|
searchSection:
|
||||||
|
showSidebar: true
|
||||||
|
|
||||||
# Collection Page Config
|
# Collection Page Config
|
||||||
collection:
|
collection:
|
||||||
|
# Search tab config
|
||||||
|
searchSection:
|
||||||
|
showSidebar: true
|
||||||
edit:
|
edit:
|
||||||
undoTimeout: 10000 # 10 seconds
|
undoTimeout: 10000 # 10 seconds
|
||||||
|
|
||||||
@@ -292,33 +387,33 @@ themes:
|
|||||||
#
|
#
|
||||||
# # A theme with a handle property will match the community, collection or item with the given
|
# # A theme with a handle property will match the community, collection or item with the given
|
||||||
# # handle, and all collections and/or items within it
|
# # handle, and all collections and/or items within it
|
||||||
# - name: 'custom',
|
# - name: custom
|
||||||
# handle: '10673/1233'
|
# handle: 10673/1233
|
||||||
#
|
#
|
||||||
# # A theme with a regex property will match the route using a regular expression. If it
|
# # A theme with a regex property will match the route using a regular expression. If it
|
||||||
# # matches the route for a community or collection it will also apply to all collections
|
# # matches the route for a community or collection it will also apply to all collections
|
||||||
# # and/or items within it
|
# # and/or items within it
|
||||||
# - name: 'custom',
|
# - name: custom
|
||||||
# regex: 'collections\/e8043bc2.*'
|
# regex: collections\/e8043bc2.*
|
||||||
#
|
#
|
||||||
# # A theme with a uuid property will match the community, collection or item with the given
|
# # A theme with a uuid property will match the community, collection or item with the given
|
||||||
# # ID, and all collections and/or items within it
|
# # ID, and all collections and/or items within it
|
||||||
# - name: 'custom',
|
# - name: custom
|
||||||
# uuid: '0958c910-2037-42a9-81c7-dca80e3892b4'
|
# uuid: 0958c910-2037-42a9-81c7-dca80e3892b4
|
||||||
#
|
#
|
||||||
# # The extends property specifies an ancestor theme (by name). Whenever a themed component is not found
|
# # The extends property specifies an ancestor theme (by name). Whenever a themed component is not found
|
||||||
# # in the current theme, its ancestor theme(s) will be checked recursively before falling back to default.
|
# # in the current theme, its ancestor theme(s) will be checked recursively before falling back to default.
|
||||||
# - name: 'custom-A',
|
# - name: custom-A
|
||||||
# extends: 'custom-B',
|
# extends: custom-B
|
||||||
# # Any of the matching properties above can be used
|
# # Any of the matching properties above can be used
|
||||||
# handle: '10673/34'
|
# handle: 10673/34
|
||||||
#
|
#
|
||||||
# - name: 'custom-B',
|
# - name: custom-B
|
||||||
# extends: 'custom',
|
# extends: custom
|
||||||
# handle: '10673/12'
|
# handle: 10673/12
|
||||||
#
|
#
|
||||||
# # A theme with only a name will match every route
|
# # A theme with only a name will match every route
|
||||||
# name: 'custom'
|
# name: custom
|
||||||
#
|
#
|
||||||
# # This theme will use the default bootstrap styling for DSpace components
|
# # This theme will use the default bootstrap styling for DSpace components
|
||||||
# - name: BASE_THEME_NAME
|
# - name: BASE_THEME_NAME
|
||||||
@@ -357,10 +452,11 @@ mediaViewer:
|
|||||||
|
|
||||||
# Whether the end user agreement is required before users use the repository.
|
# 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.
|
# 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:
|
info:
|
||||||
enableEndUserAgreement: true
|
enableEndUserAgreement: true
|
||||||
enablePrivacyStatement: true
|
enablePrivacyStatement: true
|
||||||
|
enableCOARNotifySupport: true
|
||||||
|
|
||||||
# Whether to enable Markdown (https://commonmark.org/) and MathJax (https://www.mathjax.org/)
|
# 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.
|
# display in supported metadata fields. By default, only dc.description.abstract is supported.
|
||||||
@@ -380,3 +476,91 @@ vocabularies:
|
|||||||
comcolSelectionSort:
|
comcolSelectionSort:
|
||||||
sortField: 'dc.title'
|
sortField: 'dc.title'
|
||||||
sortDirection: 'ASC'
|
sortDirection: 'ASC'
|
||||||
|
|
||||||
|
# Example of fallback collection for suggestions import
|
||||||
|
# suggestion:
|
||||||
|
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
||||||
|
# source: "openaire"
|
||||||
|
|
||||||
|
|
||||||
|
# Search settings
|
||||||
|
search:
|
||||||
|
# Settings to enable/disable or configure advanced search filters.
|
||||||
|
advancedFilters:
|
||||||
|
enabled: false
|
||||||
|
# List of filters to enable in "Advanced Search" dropdown
|
||||||
|
filter: [ 'title', 'author', 'subject', 'entityType' ]
|
||||||
|
#
|
||||||
|
# Number used to render n UI elements called loading skeletons that act as placeholders.
|
||||||
|
# These elements indicate that some content will be loaded in their stead.
|
||||||
|
# Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count.
|
||||||
|
# e.g. If we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved.
|
||||||
|
defaultFiltersCount: 5
|
||||||
|
|
||||||
|
|
||||||
|
# Notify metrics
|
||||||
|
# Configuration for Notify Admin Dashboard for metrics visualization
|
||||||
|
notifyMetrics:
|
||||||
|
# Configuration for received messages
|
||||||
|
- title: 'admin-notify-dashboard.received-ldn'
|
||||||
|
boxes:
|
||||||
|
- color: '#B8DAFF'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.incoming.accepted'
|
||||||
|
config: 'NOTIFY.incoming.accepted'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.incoming.accepted.description'
|
||||||
|
- color: '#D4EDDA'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.incoming.processed'
|
||||||
|
config: 'NOTIFY.incoming.processed'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.incoming.processed.description'
|
||||||
|
- color: '#FDBBC7'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.incoming.failure'
|
||||||
|
config: 'NOTIFY.incoming.failure'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.incoming.failure.description'
|
||||||
|
- color: '#FDBBC7'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.incoming.untrusted'
|
||||||
|
config: 'NOTIFY.incoming.untrusted'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.incoming.untrusted.description'
|
||||||
|
- color: '#43515F'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.incoming.involvedItems'
|
||||||
|
textColor: '#fff'
|
||||||
|
config: 'NOTIFY.incoming.involvedItems'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.incoming.involvedItems.description'
|
||||||
|
# Configuration for outgoing messages
|
||||||
|
- title: 'admin-notify-dashboard.generated-ldn'
|
||||||
|
boxes:
|
||||||
|
- color: '#B8DAFF'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.outgoing.queued'
|
||||||
|
config: 'NOTIFY.outgoing.queued'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.outgoing.queued.description'
|
||||||
|
- color: '#FDEEBB'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.outgoing.queued_for_retry'
|
||||||
|
config: 'NOTIFY.outgoing.queued_for_retry'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.outgoing.queued_for_retry.description'
|
||||||
|
- color: '#FDBBC7'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.outgoing.failure'
|
||||||
|
config: 'NOTIFY.outgoing.failure'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.outgoing.failure.description'
|
||||||
|
- color: '#43515F'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.outgoing.involvedItems'
|
||||||
|
textColor: '#fff'
|
||||||
|
config: 'NOTIFY.outgoing.involvedItems'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.outgoing.involvedItems.description'
|
||||||
|
- color: '#D4EDDA'
|
||||||
|
title: 'admin-notify-dashboard.NOTIFY.outgoing.delivered'
|
||||||
|
config: 'NOTIFY.outgoing.delivered'
|
||||||
|
description: 'admin-notify-dashboard.NOTIFY.outgoing.delivered.description'
|
||||||
|
|
||||||
|
|
||||||
|
# Live Region configuration
|
||||||
|
# Live Region as defined by w3c, https://www.w3.org/TR/wai-aria-1.1/#terms:
|
||||||
|
# Live regions are perceivable regions of a web page that are typically updated as a
|
||||||
|
# result of an external event when user focus may be elsewhere.
|
||||||
|
#
|
||||||
|
# The DSpace live region is a component present at the bottom of all pages that is invisible by default, but is useful
|
||||||
|
# for screen readers. Any message pushed to the live region will be announced by the screen reader. These messages
|
||||||
|
# usually contain information about changes on the page that might not be in focus.
|
||||||
|
liveRegion:
|
||||||
|
# The duration after which messages disappear from the live region in milliseconds
|
||||||
|
messageTimeOutDurationMs: 30000
|
||||||
|
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
|
||||||
|
isVisible: false
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
rest:
|
rest:
|
||||||
ssl: true
|
ssl: true
|
||||||
host: api7.dspace.org
|
host: sandbox.dspace.org
|
||||||
port: 443
|
port: 443
|
||||||
nameSpace: /server
|
nameSpace: /server
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { defineConfig } from 'cypress';
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
video: true,
|
||||||
videosFolder: 'cypress/videos',
|
videosFolder: 'cypress/videos',
|
||||||
screenshotsFolder: 'cypress/screenshots',
|
screenshotsFolder: 'cypress/screenshots',
|
||||||
fixturesFolder: 'cypress/fixtures',
|
fixturesFolder: 'cypress/fixtures',
|
||||||
@@ -9,27 +10,33 @@ export default defineConfig({
|
|||||||
openMode: 0,
|
openMode: 0,
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
// Global constants used in DSpace e2e tests (see also ./cypress/support/e2e.ts)
|
// Global DSpace environment variables used in all our Cypress e2e tests
|
||||||
// May be overridden in our cypress.json config file using specified environment variables.
|
// May be modified in this config, or overridden in a variety of ways.
|
||||||
|
// See Cypress environment variable docs: https://docs.cypress.io/guides/guides/environment-variables
|
||||||
// Default values listed here are all valid for the Demo Entities Data set available at
|
// Default values listed here are all valid for the Demo Entities Data set available at
|
||||||
// https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
// https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
||||||
// (This is the data set used in our CI environment)
|
// (This is the data set used in our CI environment)
|
||||||
|
|
||||||
// Admin account used for administrative tests
|
// Admin account used for administrative tests
|
||||||
DSPACE_TEST_ADMIN_USER: 'dspacedemo+admin@gmail.com',
|
DSPACE_TEST_ADMIN_USER: 'dspacedemo+admin@gmail.com',
|
||||||
|
DSPACE_TEST_ADMIN_USER_UUID: '335647b6-8a52-4ecb-a8c1-7ebabb199bda',
|
||||||
DSPACE_TEST_ADMIN_PASSWORD: 'dspace',
|
DSPACE_TEST_ADMIN_PASSWORD: 'dspace',
|
||||||
// Community/collection/publication used for view/edit tests
|
// Community/collection/publication used for view/edit tests
|
||||||
DSPACE_TEST_COMMUNITY: '0958c910-2037-42a9-81c7-dca80e3892b4',
|
DSPACE_TEST_COMMUNITY: '0958c910-2037-42a9-81c7-dca80e3892b4',
|
||||||
DSPACE_TEST_COLLECTION: '282164f5-d325-4740-8dd1-fa4d6d3e7200',
|
DSPACE_TEST_COLLECTION: '282164f5-d325-4740-8dd1-fa4d6d3e7200',
|
||||||
DSPACE_TEST_ENTITY_PUBLICATION: 'e98b0f27-5c19-49a0-960d-eb6ad5287067',
|
DSPACE_TEST_ENTITY_PUBLICATION: '6160810f-1e53-40db-81ef-f6621a727398',
|
||||||
// Search term (should return results) used in search tests
|
// Search term (should return results) used in search tests
|
||||||
DSPACE_TEST_SEARCH_TERM: 'test',
|
DSPACE_TEST_SEARCH_TERM: 'test',
|
||||||
// Collection used for submission tests
|
// Main Collection used for submission tests. Should be able to accept normal Item objects
|
||||||
DSPACE_TEST_SUBMIT_COLLECTION_NAME: 'Sample Collection',
|
DSPACE_TEST_SUBMIT_COLLECTION_NAME: 'Sample Collection',
|
||||||
DSPACE_TEST_SUBMIT_COLLECTION_UUID: '9d8334e9-25d3-4a67-9cea-3dffdef80144',
|
DSPACE_TEST_SUBMIT_COLLECTION_UUID: '9d8334e9-25d3-4a67-9cea-3dffdef80144',
|
||||||
|
// Collection used for Person entity submission tests. MUST be configured with EntityType=Person.
|
||||||
|
DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME: 'People',
|
||||||
// Account used to test basic submission process
|
// Account used to test basic submission process
|
||||||
DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com',
|
DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com',
|
||||||
DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace',
|
DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace',
|
||||||
|
// Administrator users group
|
||||||
|
DSPACE_ADMINISTRATOR_GROUP: 'e59f5659-bff9-451e-b28f-439e7bd467e4'
|
||||||
},
|
},
|
||||||
e2e: {
|
e2e: {
|
||||||
// Setup our plugins for e2e tests
|
// Setup our plugins for e2e tests
|
||||||
|
54
cypress/e2e/admin-add-new-modals.cy.ts
Normal file
54
cypress/e2e/admin-add-new-modals.cy.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Admin Add New Modals', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin for sidebar to appear
|
||||||
|
cy.visit('/login');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Add new Community modal should pass accessibility tests', () => {
|
||||||
|
// Pin the sidebar open
|
||||||
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('#admin-menu-section-new-title').should('be.visible');
|
||||||
|
cy.get('#admin-menu-section-new-title').click();
|
||||||
|
|
||||||
|
cy.get('a[data-test="menu.section.new_community"]').click();
|
||||||
|
|
||||||
|
// Analyze <ds-create-community-parent-selector> for accessibility
|
||||||
|
testA11y('ds-create-community-parent-selector');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Add new Collection modal should pass accessibility tests', () => {
|
||||||
|
// Pin the sidebar open
|
||||||
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('#admin-menu-section-new-title').should('be.visible');
|
||||||
|
cy.get('#admin-menu-section-new-title').click();
|
||||||
|
|
||||||
|
cy.get('a[data-test="menu.section.new_collection"]').click();
|
||||||
|
|
||||||
|
// Analyze <ds-create-collection-parent-selector> for accessibility
|
||||||
|
testA11y('ds-create-collection-parent-selector');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Add new Item modal should pass accessibility tests', () => {
|
||||||
|
// Pin the sidebar open
|
||||||
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('#admin-menu-section-new-title').should('be.visible');
|
||||||
|
cy.get('#admin-menu-section-new-title').click();
|
||||||
|
|
||||||
|
cy.get('a[data-test="menu.section.new_item"]').click();
|
||||||
|
|
||||||
|
// Analyze <ds-create-item-parent-selector> for accessibility
|
||||||
|
testA11y('ds-create-item-parent-selector');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/admin-curation-tasks.cy.ts
Normal file
16
cypress/e2e/admin-curation-tasks.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Admin Curation Tasks', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/curation-tasks');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-admin-curation-task').should('be.visible');
|
||||||
|
// Analyze <ds-admin-curation-task> for accessibility issues
|
||||||
|
testA11y('ds-admin-curation-task');
|
||||||
|
});
|
||||||
|
});
|
54
cypress/e2e/admin-edit-modals.cy.ts
Normal file
54
cypress/e2e/admin-edit-modals.cy.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Admin Edit Modals', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin for sidebar to appear
|
||||||
|
cy.visit('/login');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Edit Community modal should pass accessibility tests', () => {
|
||||||
|
// Pin the sidebar open
|
||||||
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('#admin-menu-section-edit-title').should('be.visible');
|
||||||
|
cy.get('#admin-menu-section-edit-title').click();
|
||||||
|
|
||||||
|
cy.get('a[data-test="menu.section.edit_community"]').click();
|
||||||
|
|
||||||
|
// Analyze <ds-edit-community-selector> for accessibility
|
||||||
|
testA11y('ds-edit-community-selector');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Edit Collection modal should pass accessibility tests', () => {
|
||||||
|
// Pin the sidebar open
|
||||||
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('#admin-menu-section-edit-title').should('be.visible');
|
||||||
|
cy.get('#admin-menu-section-edit-title').click();
|
||||||
|
|
||||||
|
cy.get('a[data-test="menu.section.edit_collection"]').click();
|
||||||
|
|
||||||
|
// Analyze <ds-edit-collection-selector> for accessibility
|
||||||
|
testA11y('ds-edit-collection-selector');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Edit Item modal should pass accessibility tests', () => {
|
||||||
|
// Pin the sidebar open
|
||||||
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('#admin-menu-section-edit-title').should('be.visible');
|
||||||
|
cy.get('#admin-menu-section-edit-title').click();
|
||||||
|
|
||||||
|
cy.get('a[data-test="menu.section.edit_item"]').click();
|
||||||
|
|
||||||
|
// Analyze <ds-edit-item-selector> for accessibility
|
||||||
|
testA11y('ds-edit-item-selector');
|
||||||
|
});
|
||||||
|
});
|
39
cypress/e2e/admin-export-modals.cy.ts
Normal file
39
cypress/e2e/admin-export-modals.cy.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Admin Export Modals', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin for sidebar to appear
|
||||||
|
cy.visit('/login');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Export metadata modal should pass accessibility tests', () => {
|
||||||
|
// Pin the sidebar open
|
||||||
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('#admin-menu-section-export-title').should('be.visible');
|
||||||
|
cy.get('#admin-menu-section-export-title').click();
|
||||||
|
|
||||||
|
cy.get('a[data-test="menu.section.export_metadata"]').click();
|
||||||
|
|
||||||
|
// Analyze <ds-export-metadata-selector> for accessibility
|
||||||
|
testA11y('ds-export-metadata-selector');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Export batch modal should pass accessibility tests', () => {
|
||||||
|
// Pin the sidebar open
|
||||||
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('#admin-menu-section-export-title').should('be.visible');
|
||||||
|
cy.get('#admin-menu-section-export-title').click();
|
||||||
|
|
||||||
|
cy.get('a[data-test="menu.section.export_batch"]').click();
|
||||||
|
|
||||||
|
// Analyze <ds-export-batch-selector> for accessibility
|
||||||
|
testA11y('ds-export-batch-selector');
|
||||||
|
});
|
||||||
|
});
|
17
cypress/e2e/admin-notifications-publication-claim-page.cy.ts
Normal file
17
cypress/e2e/admin-notifications-publication-claim-page.cy.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Admin Notifications Publication Claim Page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/notifications/publication-claim');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
|
||||||
|
//Page must first be visible
|
||||||
|
cy.get('ds-admin-notifications-publication-claim-page').should('be.visible');
|
||||||
|
// Analyze <ds-admin-notifications-publication-claim-page> for accessibility issues
|
||||||
|
testA11y('ds-admin-notifications-publication-claim-page');
|
||||||
|
});
|
||||||
|
});
|
21
cypress/e2e/admin-search-page.cy.ts
Normal file
21
cypress/e2e/admin-search-page.cy.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Admin Search Page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/search');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
//Page must first be visible
|
||||||
|
cy.get('ds-admin-search-page').should('be.visible');
|
||||||
|
// At least one search result should be displayed
|
||||||
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
|
// Click each filter toggle to open *every* filter
|
||||||
|
// (As we want to scan filter section for accessibility issues as well)
|
||||||
|
cy.get('[data-test="filter-toggle"]').click({ multiple: true });
|
||||||
|
// Analyze <ds-admin-search-page> for accessibility issues
|
||||||
|
testA11y('ds-admin-search-page');
|
||||||
|
});
|
||||||
|
});
|
28
cypress/e2e/admin-sidebar.cy.ts
Normal file
28
cypress/e2e/admin-sidebar.cy.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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'));
|
||||||
|
});
|
||||||
|
|
||||||
|
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 });
|
||||||
|
|
||||||
|
// Analyze <ds-admin-sidebar> 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 },
|
||||||
|
},
|
||||||
|
} as Options);
|
||||||
|
});
|
||||||
|
});
|
21
cypress/e2e/admin-workflow-page.cy.ts
Normal file
21
cypress/e2e/admin-workflow-page.cy.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Admin Workflow Page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/workflow');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-admin-workflow-page').should('be.visible');
|
||||||
|
// At least one search result should be displayed
|
||||||
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
|
// Click each filter toggle to open *every* filter
|
||||||
|
// (As we want to scan filter section for accessibility issues as well)
|
||||||
|
cy.get('[data-test="filter-toggle"]').click({ multiple: true });
|
||||||
|
// Analyze <ds-admin-workflow-page> for accessibility issues
|
||||||
|
testA11y('ds-admin-workflow-page');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/batch-import-page.cy.ts
Normal file
16
cypress/e2e/batch-import-page.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Batch Import Page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see processes
|
||||||
|
cy.visit('/admin/batch-import');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Batch import form must first be visible
|
||||||
|
cy.get('ds-batch-import-page').should('be.visible');
|
||||||
|
// Analyze <ds-batch-import-page> for accessibility issues
|
||||||
|
testA11y('ds-batch-import-page');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/bitstreams-format.cy.ts
Normal file
16
cypress/e2e/bitstreams-format.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Bitstreams Formats', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/registries/bitstream-formats');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-bitstream-formats').should('be.visible');
|
||||||
|
// Analyze <ds-bitstream-formats> for accessibility issues
|
||||||
|
testA11y('ds-bitstream-formats');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,10 +1,9 @@
|
|||||||
import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Breadcrumbs', () => {
|
describe('Breadcrumbs', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
// Visit an Item, as those have more breadcrumbs
|
// Visit an Item, as those have more breadcrumbs
|
||||||
cy.visit('/entities/publication/'.concat(TEST_ENTITY_PUBLICATION));
|
cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
|
||||||
|
|
||||||
// Wait for breadcrumbs to be visible
|
// Wait for breadcrumbs to be visible
|
||||||
cy.get('ds-breadcrumbs').should('be.visible');
|
cy.get('ds-breadcrumbs').should('be.visible');
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Author', () => {
|
|||||||
cy.visit('/browse/author');
|
cy.visit('/browse/author');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-metadata-page> to be visible
|
// Wait for <ds-browse-by-metadata-page> to be visible
|
||||||
cy.get('ds-browse-by-metadata-page').should('be.visible');
|
cy.get('ds-browse-by-metadata').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-metadata-page> for accessibility
|
// Analyze <ds-browse-by-metadata-page> for accessibility
|
||||||
testA11y('ds-browse-by-metadata-page');
|
testA11y('ds-browse-by-metadata');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Date Issued', () => {
|
|||||||
cy.visit('/browse/dateissued');
|
cy.visit('/browse/dateissued');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-date-page> to be visible
|
// Wait for <ds-browse-by-date-page> to be visible
|
||||||
cy.get('ds-browse-by-date-page').should('be.visible');
|
cy.get('ds-browse-by-date').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-date-page> for accessibility
|
// Analyze <ds-browse-by-date-page> for accessibility
|
||||||
testA11y('ds-browse-by-date-page');
|
testA11y('ds-browse-by-date');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Subject', () => {
|
|||||||
cy.visit('/browse/subject');
|
cy.visit('/browse/subject');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-metadata-page> to be visible
|
// Wait for <ds-browse-by-metadata-page> to be visible
|
||||||
cy.get('ds-browse-by-metadata-page').should('be.visible');
|
cy.get('ds-browse-by-metadata').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-metadata-page> for accessibility
|
// Analyze <ds-browse-by-metadata-page> for accessibility
|
||||||
testA11y('ds-browse-by-metadata-page');
|
testA11y('ds-browse-by-metadata');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Title', () => {
|
|||||||
cy.visit('/browse/title');
|
cy.visit('/browse/title');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-title-page> to be visible
|
// Wait for <ds-browse-by-title-page> to be visible
|
||||||
cy.get('ds-browse-by-title-page').should('be.visible');
|
cy.get('ds-browse-by-title').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-title-page> for accessibility
|
// Analyze <ds-browse-by-title-page> for accessibility
|
||||||
testA11y('ds-browse-by-title-page');
|
testA11y('ds-browse-by-title');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
31
cypress/e2e/bulk-access.cy.ts
Normal file
31
cypress/e2e/bulk-access.cy.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
import { Options } from 'cypress-axe';
|
||||||
|
|
||||||
|
describe('Bulk Access', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/access-control/bulk-access');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-bulk-access').should('be.visible');
|
||||||
|
// At least one search result should be displayed
|
||||||
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
|
// Click each filter toggle to open *every* filter
|
||||||
|
// (As we want to scan filter section for accessibility issues as well)
|
||||||
|
cy.get('[data-test="filter-toggle"]').click({ multiple: true });
|
||||||
|
// Analyze <ds-bulk-access> for accessibility issues
|
||||||
|
testA11y('ds-bulk-access', {
|
||||||
|
rules: {
|
||||||
|
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
|
||||||
|
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
||||||
|
'aria-required-children': { enabled: false },
|
||||||
|
'nested-interactive': { enabled: false },
|
||||||
|
// Card titles fail this test currently
|
||||||
|
'heading-order': { enabled: false },
|
||||||
|
},
|
||||||
|
} as Options);
|
||||||
|
});
|
||||||
|
});
|
13
cypress/e2e/collection-create.cy.ts
Normal file
13
cypress/e2e/collection-create.cy.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/collections/create?parent='.concat(Cypress.env('DSPACE_TEST_COMMUNITY')));
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show loading component while saving', () => {
|
||||||
|
const title = 'Test Collection Title';
|
||||||
|
cy.get('#title').type(title);
|
||||||
|
|
||||||
|
cy.get('button[type="submit"]').click();
|
||||||
|
|
||||||
|
cy.get('ds-loading').should('be.visible');
|
||||||
|
});
|
128
cypress/e2e/collection-edit.cy.ts
Normal file
128
cypress/e2e/collection-edit.cy.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
// 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', () => {
|
||||||
|
// <ds-edit-collection> tag must be loaded
|
||||||
|
cy.get('ds-edit-collection').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze <ds-edit-collection> 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();
|
||||||
|
|
||||||
|
// <ds-collection-roles> tag must be loaded
|
||||||
|
cy.get('ds-collection-roles').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// <ds-collection-source> 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();
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edit Collection > Curate tab', () => {
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.get('a[data-test="curate"]').click();
|
||||||
|
|
||||||
|
// <ds-collection-curate> tag must be loaded
|
||||||
|
cy.get('ds-collection-curate').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// <ds-collection-access-control> tag must be loaded
|
||||||
|
cy.get('ds-collection-access-control').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// <ds-collection-authorizations> tag must be loaded
|
||||||
|
cy.get('ds-collection-authorizations').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// <ds-collection-item-mapper> tag must be loaded
|
||||||
|
cy.get('ds-collection-item-mapper').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Edit Collection > Delete page', () => {
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.get('a[data-test="delete-button"]').click();
|
||||||
|
|
||||||
|
// <ds-delete-collection> tag must be loaded
|
||||||
|
cy.get('ds-delete-collection').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-delete-collection');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,10 +1,9 @@
|
|||||||
import { TEST_COLLECTION } from 'cypress/support/e2e';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Collection Page', () => {
|
describe('Collection Page', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit('/collections/'.concat(TEST_COLLECTION));
|
cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')));
|
||||||
|
|
||||||
// <ds-collection-page> tag must be loaded
|
// <ds-collection-page> tag must be loaded
|
||||||
cy.get('ds-collection-page').should('be.visible');
|
cy.get('ds-collection-page').should('be.visible');
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_COLLECTION } from 'cypress/support/e2e';
|
import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Collection Statistics Page', () => {
|
describe('Collection Statistics Page', () => {
|
||||||
const COLLECTIONSTATISTICSPAGE = '/statistics/collections/'.concat(TEST_COLLECTION);
|
const COLLECTIONSTATISTICSPAGE = '/statistics/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION'));
|
||||||
|
|
||||||
it('should load if you click on "Statistics" from a Collection page', () => {
|
it('should load if you click on "Statistics" from a Collection page', () => {
|
||||||
cy.visit('/collections/'.concat(TEST_COLLECTION));
|
cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')));
|
||||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
||||||
cy.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE);
|
cy.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ describe('Collection Statistics Page', () => {
|
|||||||
it('should contain a "Total visits per month" section', () => {
|
it('should contain a "Total visits per month" section', () => {
|
||||||
cy.visit(COLLECTIONSTATISTICSPAGE);
|
cy.visit(COLLECTIONSTATISTICSPAGE);
|
||||||
// Check just for existence because this table is empty in CI environment as it's historical data
|
// Check just for existence because this table is empty in CI environment as it's historical data
|
||||||
cy.get('.'.concat(TEST_COLLECTION).concat('_TotalVisitsPerMonth')).should('exist');
|
cy.get('.'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('_TotalVisitsPerMonth')).should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
|
13
cypress/e2e/community-create.cy.ts
Normal file
13
cypress/e2e/community-create.cy.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/communities/create');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show loading component while saving', () => {
|
||||||
|
const title = 'Test Community Title';
|
||||||
|
cy.get('#title').type(title);
|
||||||
|
|
||||||
|
cy.get('button[type="submit"]').click();
|
||||||
|
|
||||||
|
cy.get('ds-loading').should('be.visible');
|
||||||
|
});
|
86
cypress/e2e/community-edit.cy.ts
Normal file
86
cypress/e2e/community-edit.cy.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
// 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', () => {
|
||||||
|
// <ds-edit-community> tag must be loaded
|
||||||
|
cy.get('ds-edit-community').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze <ds-edit-community> 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();
|
||||||
|
|
||||||
|
// <ds-community-roles> tag must be loaded
|
||||||
|
cy.get('ds-community-roles').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// <ds-community-curate> tag must be loaded
|
||||||
|
cy.get('ds-community-curate').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// <ds-community-access-control> tag must be loaded
|
||||||
|
cy.get('ds-community-access-control').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// <ds-community-authorizations> tag must be loaded
|
||||||
|
cy.get('ds-community-authorizations').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// <ds-delete-community> tag must be loaded
|
||||||
|
cy.get('ds-delete-community').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-delete-community');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,4 +1,3 @@
|
|||||||
import { Options } from 'cypress-axe';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Community List Page', () => {
|
describe('Community List Page', () => {
|
||||||
@@ -13,13 +12,6 @@ describe('Community List Page', () => {
|
|||||||
cy.get('[data-test="expand-button"]').click({ multiple: true });
|
cy.get('[data-test="expand-button"]').click({ multiple: true });
|
||||||
|
|
||||||
// Analyze <ds-community-list-page> for accessibility issues
|
// Analyze <ds-community-list-page> for accessibility issues
|
||||||
// Disable heading-order checks until it is fixed
|
testA11y('ds-community-list-page');
|
||||||
testA11y('ds-community-list-page',
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
'heading-order': { enabled: false }
|
|
||||||
}
|
|
||||||
} as Options
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
import { TEST_COMMUNITY } from 'cypress/support/e2e';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Community Page', () => {
|
describe('Community Page', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit('/communities/'.concat(TEST_COMMUNITY));
|
cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')));
|
||||||
|
|
||||||
// <ds-community-page> tag must be loaded
|
// <ds-community-page> tag must be loaded
|
||||||
cy.get('ds-community-page').should('be.visible');
|
cy.get('ds-community-page').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-community-page> for accessibility issues
|
// Analyze <ds-community-page> for accessibility issues
|
||||||
testA11y('ds-community-page',);
|
testA11y('ds-community-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_COMMUNITY } from 'cypress/support/e2e';
|
import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Community Statistics Page', () => {
|
describe('Community Statistics Page', () => {
|
||||||
const COMMUNITYSTATISTICSPAGE = '/statistics/communities/'.concat(TEST_COMMUNITY);
|
const COMMUNITYSTATISTICSPAGE = '/statistics/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY'));
|
||||||
|
|
||||||
it('should load if you click on "Statistics" from a Community page', () => {
|
it('should load if you click on "Statistics" from a Community page', () => {
|
||||||
cy.visit('/communities/'.concat(TEST_COMMUNITY));
|
cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')));
|
||||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
||||||
cy.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE);
|
cy.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ describe('Community Statistics Page', () => {
|
|||||||
it('should contain a "Total visits per month" section', () => {
|
it('should contain a "Total visits per month" section', () => {
|
||||||
cy.visit(COMMUNITYSTATISTICSPAGE);
|
cy.visit(COMMUNITYSTATISTICSPAGE);
|
||||||
// Check just for existence because this table is empty in CI environment as it's historical data
|
// Check just for existence because this table is empty in CI environment as it's historical data
|
||||||
cy.get('.'.concat(TEST_COMMUNITY).concat('_TotalVisitsPerMonth')).should('exist');
|
cy.get('.'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')).concat('_TotalVisitsPerMonth')).should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
|
16
cypress/e2e/create-eperson.cy.ts
Normal file
16
cypress/e2e/create-eperson.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Create Eperson', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/access-control/epeople/create');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Form must first be visible
|
||||||
|
cy.get('ds-eperson-form').should('be.visible');
|
||||||
|
// Analyze <ds-eperson-form> for accessibility issues
|
||||||
|
testA11y('ds-eperson-form');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/create-group.cy.ts
Normal file
16
cypress/e2e/create-group.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Create Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/access-control/groups/create');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Form must first be visible
|
||||||
|
cy.get('ds-group-form').should('be.visible');
|
||||||
|
// Analyze <ds-group-form> for accessibility issues
|
||||||
|
testA11y('ds-group-form');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/edit-eperson.cy.ts
Normal file
16
cypress/e2e/edit-eperson.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Edit Eperson', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/access-control/epeople/'.concat(Cypress.env('DSPACE_TEST_ADMIN_USER_UUID')).concat('/edit'));
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Form must first be visible
|
||||||
|
cy.get('ds-eperson-form').should('be.visible');
|
||||||
|
// Analyze <ds-eperson-form> for accessibility issues
|
||||||
|
testA11y('ds-eperson-form');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/edit-group.cy.ts
Normal file
16
cypress/e2e/edit-group.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Edit Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/access-control/groups/'.concat(Cypress.env('DSPACE_ADMINISTRATOR_GROUP')).concat('/edit'));
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Form must first be visible
|
||||||
|
cy.get('ds-group-form').should('be.visible');
|
||||||
|
// Analyze <ds-group-form> for accessibility issues
|
||||||
|
testA11y('ds-group-form');
|
||||||
|
});
|
||||||
|
});
|
13
cypress/e2e/end-user-agreement.cy.ts
Normal file
13
cypress/e2e/end-user-agreement.cy.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('End User Agreement', () => {
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.visit('/info/end-user-agreement');
|
||||||
|
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-end-user-agreement').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze <ds-end-user-agreement> for accessibility
|
||||||
|
testA11y('ds-end-user-agreement');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/epeople-registry.cy.ts
Normal file
16
cypress/e2e/epeople-registry.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Epeople registry', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/access-control/epeople');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Epeople registry page must first be visible
|
||||||
|
cy.get('ds-epeople-registry').should('be.visible');
|
||||||
|
// Analyze <ds-epeople-registry> for accessibility issues
|
||||||
|
testA11y('ds-epeople-registry');
|
||||||
|
});
|
||||||
|
});
|
13
cypress/e2e/feedback.cy.ts
Normal file
13
cypress/e2e/feedback.cy.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Feedback', () => {
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.visit('/info/feedback');
|
||||||
|
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-feedback').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze <ds-feedback> for accessibility
|
||||||
|
testA11y('ds-feedback');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/groups-registry.cy.ts
Normal file
16
cypress/e2e/groups-registry.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Groups registry', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/access-control/groups');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Epeople registry page must first be visible
|
||||||
|
cy.get('ds-groups-registry').should('be.visible');
|
||||||
|
// Analyze <ds-groups-registry> for accessibility issues
|
||||||
|
testA11y('ds-groups-registry');
|
||||||
|
});
|
||||||
|
});
|
@@ -8,12 +8,31 @@ describe('Header', () => {
|
|||||||
cy.get('ds-header').should('be.visible');
|
cy.get('ds-header').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-header> for accessibility
|
// Analyze <ds-header> for accessibility
|
||||||
testA11y({
|
testA11y('ds-header');
|
||||||
include: ['ds-header'],
|
});
|
||||||
exclude: [
|
|
||||||
['#search-navbar-container'], // search in navbar has duplicative ID. Will be fixed in #1174
|
it('should allow for changing language to German (for example)', () => {
|
||||||
['.dropdownLogin'] // "Log in" link has color contrast issues. Will be fixed in #1149
|
cy.visit('/');
|
||||||
],
|
|
||||||
});
|
// Click the language switcher (globe) in header
|
||||||
|
cy.get('a[data-test="lang-switch"]').click();
|
||||||
|
// Click on the "Deusch" language in dropdown
|
||||||
|
cy.get('#language-menu-list li').contains('Deutsch').click();
|
||||||
|
|
||||||
|
// HTML "lang" attribute should switch to "de"
|
||||||
|
cy.get('html').invoke('attr', 'lang').should('eq', 'de');
|
||||||
|
|
||||||
|
// Login menu should now be in German
|
||||||
|
cy.get('a[data-test="login-menu"]').contains('Anmelden');
|
||||||
|
|
||||||
|
// Change back to English from language switcher
|
||||||
|
cy.get('a[data-test="lang-switch"]').click();
|
||||||
|
cy.get('#language-menu-list li').contains('English').click();
|
||||||
|
|
||||||
|
// HTML "lang" attribute should switch to "en"
|
||||||
|
cy.get('html').invoke('attr', 'lang').should('eq', 'en');
|
||||||
|
|
||||||
|
// Login menu should now be in English
|
||||||
|
cy.get('a[data-test="login-menu"]').contains('Log In');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
62
cypress/e2e/health-page.cy.ts
Normal file
62
cypress/e2e/health-page.cy.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
import { Options } from 'cypress-axe';
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/health');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Health Page > Status Tab', () => {
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.intercept('GET', '/server/actuator/health').as('status');
|
||||||
|
cy.wait('@status');
|
||||||
|
|
||||||
|
cy.get('a[data-test="health-page.status-tab"]').click();
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-health-page').should('be.visible');
|
||||||
|
cy.get('ds-health-panel').should('be.visible');
|
||||||
|
|
||||||
|
// wait for all the ds-health-info-component components to be rendered
|
||||||
|
cy.get('div[role="tabpanel"]').each(($panel: HTMLDivElement) => {
|
||||||
|
cy.wrap($panel).find('ds-health-component').should('be.visible');
|
||||||
|
});
|
||||||
|
// Analyze <ds-health-page> for accessibility issues
|
||||||
|
testA11y('ds-health-page', {
|
||||||
|
rules: {
|
||||||
|
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
|
||||||
|
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
||||||
|
'aria-required-children': { enabled: false },
|
||||||
|
'nested-interactive': { enabled: false },
|
||||||
|
},
|
||||||
|
} as Options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Health Page > Info Tab', () => {
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.intercept('GET', '/server/actuator/info').as('info');
|
||||||
|
cy.wait('@info');
|
||||||
|
|
||||||
|
cy.get('a[data-test="health-page.info-tab"]').click();
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-health-page').should('be.visible');
|
||||||
|
cy.get('ds-health-info').should('be.visible');
|
||||||
|
|
||||||
|
// wait for all the ds-health-info-component components to be rendered
|
||||||
|
cy.get('div[role="tabpanel"]').each(($panel: HTMLDivElement) => {
|
||||||
|
cy.wrap($panel).find('ds-health-info-component').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Analyze <ds-health-info> for accessibility issues
|
||||||
|
testA11y('ds-health-info', {
|
||||||
|
rules: {
|
||||||
|
// All panels are accordions & fail "aria-required-children" and "nested-interactive".
|
||||||
|
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
||||||
|
'aria-required-children': { enabled: false },
|
||||||
|
'nested-interactive': { enabled: false },
|
||||||
|
},
|
||||||
|
} as Options);
|
||||||
|
});
|
||||||
|
});
|
@@ -1,18 +1,19 @@
|
|||||||
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
|
||||||
import '../support/commands';
|
import '../support/commands';
|
||||||
|
|
||||||
|
import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e';
|
||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Site Statistics Page', () => {
|
describe('Site Statistics Page', () => {
|
||||||
it('should load if you click on "Statistics" from homepage', () => {
|
it('should load if you click on "Statistics" from homepage', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
||||||
cy.location('pathname').should('eq', '/statistics');
|
cy.location('pathname').should('eq', '/statistics');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
// generate 2 view events on an Item's page
|
// generate 2 view events on an Item's page
|
||||||
cy.generateViewEvent(TEST_ENTITY_PUBLICATION, 'item');
|
cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item');
|
||||||
cy.generateViewEvent(TEST_ENTITY_PUBLICATION, 'item');
|
cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item');
|
||||||
|
|
||||||
cy.visit('/statistics');
|
cy.visit('/statistics');
|
||||||
|
|
||||||
|
180
cypress/e2e/item-edit.cy.ts
Normal file
180
cypress/e2e/item-edit.cy.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
// 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"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="metadata"]').click();
|
||||||
|
|
||||||
|
// Our selected tab should be both visible & active
|
||||||
|
cy.get('a[data-test="metadata"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="metadata"]').should('have.class', 'active');
|
||||||
|
|
||||||
|
// <ds-edit-item-page> tag must be loaded
|
||||||
|
cy.get('ds-edit-item-page').should('be.visible');
|
||||||
|
|
||||||
|
// wait for all the ds-dso-edit-metadata-value components to be rendered
|
||||||
|
cy.get('ds-dso-edit-metadata-value div[role="row"]').each(($row: HTMLDivElement) => {
|
||||||
|
cy.wrap($row).find('div[role="cell"]').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Analyze <ds-edit-item-page> for accessibility issues
|
||||||
|
testA11y('ds-edit-item-page');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edit Item > Status tab', () => {
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.get('a[data-test="status"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="status"]').click();
|
||||||
|
|
||||||
|
// Our selected tab should be both visible & active
|
||||||
|
cy.get('a[data-test="status"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="status"]').should('have.class', 'active');
|
||||||
|
|
||||||
|
// <ds-item-status> tag must be loaded
|
||||||
|
cy.get('ds-item-status').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-item-status');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edit Item > Bitstreams tab', () => {
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.get('a[data-test="bitstreams"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="bitstreams"]').click();
|
||||||
|
|
||||||
|
// Our selected tab should be both visible & active
|
||||||
|
cy.get('a[data-test="bitstreams"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="bitstreams"]').should('have.class', 'active');
|
||||||
|
|
||||||
|
// <ds-item-bitstreams> tag must be loaded
|
||||||
|
cy.get('ds-item-bitstreams').should('be.visible');
|
||||||
|
|
||||||
|
// Table of item bitstreams must also be loaded
|
||||||
|
cy.get('div.item-bitstreams').should('be.visible');
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edit Item > Curate tab', () => {
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.get('a[data-test="curate"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="curate"]').click();
|
||||||
|
|
||||||
|
// Our selected tab should be both visible & active
|
||||||
|
cy.get('a[data-test="curate"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="curate"]').should('have.class', 'active');
|
||||||
|
|
||||||
|
// <ds-item-curate> tag must be loaded
|
||||||
|
cy.get('ds-item-curate').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-item-curate');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edit Item > Relationships tab', () => {
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.get('a[data-test="relationships"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="relationships"]').click();
|
||||||
|
|
||||||
|
// Our selected tab should be both visible & active
|
||||||
|
cy.get('a[data-test="relationships"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="relationships"]').should('have.class', 'active');
|
||||||
|
|
||||||
|
// <ds-item-relationships> tag must be loaded
|
||||||
|
cy.get('ds-item-relationships').should('be.visible');
|
||||||
|
|
||||||
|
// 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"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="versionhistory"]').click();
|
||||||
|
|
||||||
|
// Our selected tab should be both visible & active
|
||||||
|
cy.get('a[data-test="versionhistory"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="versionhistory"]').should('have.class', 'active');
|
||||||
|
|
||||||
|
// <ds-item-version-history> tag must be loaded
|
||||||
|
cy.get('ds-item-version-history').should('be.visible');
|
||||||
|
|
||||||
|
// 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"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="access-control"]').click();
|
||||||
|
|
||||||
|
// Our selected tab should be both visible & active
|
||||||
|
cy.get('a[data-test="access-control"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="access-control"]').should('have.class', 'active');
|
||||||
|
|
||||||
|
// <ds-item-access-control> tag must be loaded
|
||||||
|
cy.get('ds-item-access-control').should('be.visible');
|
||||||
|
|
||||||
|
// 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"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="mapper"]').click();
|
||||||
|
|
||||||
|
// Our selected tab should be both visible & active
|
||||||
|
cy.get('a[data-test="mapper"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="mapper"]').should('have.class', 'active');
|
||||||
|
|
||||||
|
// <ds-item-collection-mapper> tag must be loaded
|
||||||
|
cy.get('ds-item-collection-mapper').should('be.visible');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,10 +1,8 @@
|
|||||||
import { Options } from 'cypress-axe';
|
|
||||||
import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Item Page', () => {
|
describe('Item Page', () => {
|
||||||
const ITEMPAGE = '/items/'.concat(TEST_ENTITY_PUBLICATION);
|
const ITEMPAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
|
||||||
const ENTITYPAGE = '/entities/publication/'.concat(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]
|
// 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', () => {
|
it('should redirect to the entity page when navigating to an item page', () => {
|
||||||
@@ -19,13 +17,16 @@ describe('Item Page', () => {
|
|||||||
cy.get('ds-item-page').should('be.visible');
|
cy.get('ds-item-page').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-item-page> for accessibility issues
|
// Analyze <ds-item-page> for accessibility issues
|
||||||
// Disable heading-order checks until it is fixed
|
testA11y('ds-item-page');
|
||||||
testA11y('ds-item-page',
|
});
|
||||||
{
|
|
||||||
rules: {
|
it('should pass accessibility tests on full item page', () => {
|
||||||
'heading-order': { enabled: false }
|
cy.visit(ENTITYPAGE + '/full');
|
||||||
}
|
|
||||||
} as Options
|
// <ds-full-item-page> tag must be loaded
|
||||||
);
|
cy.get('ds-full-item-page').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze <ds-full-item-page> for accessibility issues
|
||||||
|
testA11y('ds-full-item-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Item Statistics Page', () => {
|
describe('Item Statistics Page', () => {
|
||||||
const ITEMSTATISTICSPAGE = '/statistics/items/'.concat(TEST_ENTITY_PUBLICATION);
|
const ITEMSTATISTICSPAGE = '/statistics/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
|
||||||
|
|
||||||
it('should load if you click on "Statistics" from an Item/Entity page', () => {
|
it('should load if you click on "Statistics" from an Item/Entity page', () => {
|
||||||
cy.visit('/entities/publication/'.concat(TEST_ENTITY_PUBLICATION));
|
cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
|
||||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
||||||
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
|
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ describe('Item Statistics Page', () => {
|
|||||||
it('should contain a "Total visits per month" section', () => {
|
it('should contain a "Total visits per month" section', () => {
|
||||||
cy.visit(ITEMSTATISTICSPAGE);
|
cy.visit(ITEMSTATISTICSPAGE);
|
||||||
// Check just for existence because this table is empty in CI environment as it's historical data
|
// Check just for existence because this table is empty in CI environment as it's historical data
|
||||||
cy.get('.'.concat(TEST_ENTITY_PUBLICATION).concat('_TotalVisitsPerMonth')).should('exist');
|
cy.get('.'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')).concat('_TotalVisitsPerMonth')).should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
|
15
cypress/e2e/item-template.cy.ts
Normal file
15
cypress/e2e/item-template.cy.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const ADD_TEMPLATE_ITEM_PAGE = '/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('/itemtemplate');
|
||||||
|
|
||||||
|
describe('Item Template', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit(ADD_TEMPLATE_ITEM_PAGE);
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load properly', () => {
|
||||||
|
cy.contains('.ds-header-row .lbl-cell', 'Field', { timeout: 10000 }).should('exist').should('be.visible');
|
||||||
|
cy.contains('.ds-header-row b', 'Value', { timeout: 10000 }).should('exist').should('be.visible');
|
||||||
|
cy.contains('.ds-header-row b', 'Lang', { timeout: 10000 }).should('exist').should('be.visible');
|
||||||
|
cy.contains('.ds-header-row b', 'Edit', { timeout: 10000 }).should('exist').should('be.visible');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,42 +1,42 @@
|
|||||||
import { TEST_ADMIN_PASSWORD, TEST_ADMIN_USER, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
const page = {
|
const page = {
|
||||||
openLoginMenu() {
|
openLoginMenu() {
|
||||||
// Click the "Log In" dropdown menu in header
|
// Click the "Log In" dropdown menu in header
|
||||||
cy.get('ds-themed-navbar [data-test="login-menu"]').click();
|
cy.get('[data-test="login-menu"]').click();
|
||||||
},
|
},
|
||||||
openUserMenu() {
|
openUserMenu() {
|
||||||
// Once logged in, click the User menu in header
|
// Once logged in, click the User menu in header
|
||||||
cy.get('ds-themed-navbar [data-test="user-menu"]').click();
|
cy.get('[data-test="user-menu"]').click();
|
||||||
},
|
},
|
||||||
submitLoginAndPasswordByPressingButton(email, password) {
|
submitLoginAndPasswordByPressingButton(email, password) {
|
||||||
// Enter email
|
// Enter email
|
||||||
cy.get('ds-themed-navbar [data-test="email"]').type(email);
|
cy.get('[data-test="email"]').type(email);
|
||||||
// Enter password
|
// Enter password
|
||||||
cy.get('ds-themed-navbar [data-test="password"]').type(password);
|
cy.get('[data-test="password"]').type(password);
|
||||||
// Click login button
|
// Click login button
|
||||||
cy.get('ds-themed-navbar [data-test="login-button"]').click();
|
cy.get('[data-test="login-button"]').click();
|
||||||
},
|
},
|
||||||
submitLoginAndPasswordByPressingEnter(email, password) {
|
submitLoginAndPasswordByPressingEnter(email, password) {
|
||||||
// In opened Login modal, fill out email & password, then click Enter
|
// In opened Login modal, fill out email & password, then click Enter
|
||||||
cy.get('ds-themed-navbar [data-test="email"]').type(email);
|
cy.get('[data-test="email"]').type(email);
|
||||||
cy.get('ds-themed-navbar [data-test="password"]').type(password);
|
cy.get('[data-test="password"]').type(password);
|
||||||
cy.get('ds-themed-navbar [data-test="password"]').type('{enter}');
|
cy.get('[data-test="password"]').type('{enter}');
|
||||||
},
|
},
|
||||||
submitLogoutByPressingButton() {
|
submitLogoutByPressingButton() {
|
||||||
// This is the POST command that will actually log us out
|
// This is the POST command that will actually log us out
|
||||||
cy.intercept('POST', '/server/api/authn/logout').as('logout');
|
cy.intercept('POST', '/server/api/authn/logout').as('logout');
|
||||||
// Click logout button
|
// Click logout button
|
||||||
cy.get('ds-themed-navbar [data-test="logout-button"]').click();
|
cy.get('[data-test="logout-button"]').click();
|
||||||
// Wait until above POST command responds before continuing
|
// Wait until above POST command responds before continuing
|
||||||
// (This ensures next action waits until logout completes)
|
// (This ensures next action waits until logout completes)
|
||||||
cy.wait('@logout');
|
cy.wait('@logout');
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Login Modal', () => {
|
describe('Login Modal', () => {
|
||||||
it('should login when clicking button & stay on same page', () => {
|
it('should login when clicking button & stay on same page', () => {
|
||||||
const ENTITYPAGE = '/entities/publication/'.concat(TEST_ENTITY_PUBLICATION);
|
const ENTITYPAGE = '/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
|
||||||
cy.visit(ENTITYPAGE);
|
cy.visit(ENTITYPAGE);
|
||||||
|
|
||||||
// Login menu should exist
|
// Login menu should exist
|
||||||
@@ -46,7 +46,7 @@ describe('Login Modal', () => {
|
|||||||
page.openLoginMenu();
|
page.openLoginMenu();
|
||||||
cy.get('.form-login').should('be.visible');
|
cy.get('.form-login').should('be.visible');
|
||||||
|
|
||||||
page.submitLoginAndPasswordByPressingButton(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD);
|
page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
cy.get('ds-log-in').should('not.exist');
|
cy.get('ds-log-in').should('not.exist');
|
||||||
|
|
||||||
// Verify we are still on the same page
|
// Verify we are still on the same page
|
||||||
@@ -66,8 +66,8 @@ describe('Login Modal', () => {
|
|||||||
cy.get('.form-login').should('be.visible');
|
cy.get('.form-login').should('be.visible');
|
||||||
|
|
||||||
// Login, and the <ds-log-in> tag should no longer exist
|
// Login, and the <ds-log-in> tag should no longer exist
|
||||||
page.submitLoginAndPasswordByPressingEnter(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD);
|
page.submitLoginAndPasswordByPressingEnter(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
cy.get('.form-login').should('not.exist');
|
cy.get('ds-log-in').should('not.exist');
|
||||||
|
|
||||||
// Verify we are still on homepage
|
// Verify we are still on homepage
|
||||||
cy.url().should('include', '/home');
|
cy.url().should('include', '/home');
|
||||||
@@ -80,7 +80,7 @@ describe('Login Modal', () => {
|
|||||||
|
|
||||||
it('should support logout', () => {
|
it('should support logout', () => {
|
||||||
// First authenticate & access homepage
|
// First authenticate & access homepage
|
||||||
cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD);
|
cy.login(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
// Verify ds-log-in tag doesn't exist, but ds-log-out tag does exist
|
// Verify ds-log-in tag doesn't exist, but ds-log-out tag does exist
|
||||||
@@ -102,12 +102,15 @@ describe('Login Modal', () => {
|
|||||||
page.openLoginMenu();
|
page.openLoginMenu();
|
||||||
|
|
||||||
// Registration link should be visible
|
// Registration link should be visible
|
||||||
cy.get('ds-themed-navbar [data-test="register"]').should('be.visible');
|
cy.get('ds-header [data-test="register"]').should('be.visible');
|
||||||
|
|
||||||
// Click registration link & you should go to registration page
|
// Click registration link & you should go to registration page
|
||||||
cy.get('ds-themed-navbar [data-test="register"]').click();
|
cy.get('ds-header [data-test="register"]').click();
|
||||||
cy.location('pathname').should('eq', '/register');
|
cy.location('pathname').should('eq', '/register');
|
||||||
cy.get('ds-register-email').should('exist');
|
cy.get('ds-register-email').should('exist');
|
||||||
|
|
||||||
|
// Test accessibility of this page
|
||||||
|
testA11y('ds-register-email');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow forgot password', () => {
|
it('should allow forgot password', () => {
|
||||||
@@ -116,11 +119,32 @@ describe('Login Modal', () => {
|
|||||||
page.openLoginMenu();
|
page.openLoginMenu();
|
||||||
|
|
||||||
// Forgot password link should be visible
|
// Forgot password link should be visible
|
||||||
cy.get('ds-themed-navbar [data-test="forgot"]').should('be.visible');
|
cy.get('ds-header [data-test="forgot"]').should('be.visible');
|
||||||
|
|
||||||
// Click link & you should go to Forgot Password page
|
// Click link & you should go to Forgot Password page
|
||||||
cy.get('ds-themed-navbar [data-test="forgot"]').click();
|
cy.get('ds-header [data-test="forgot"]').click();
|
||||||
cy.location('pathname').should('eq', '/forgot');
|
cy.location('pathname').should('eq', '/forgot');
|
||||||
cy.get('ds-forgot-email').should('exist');
|
cy.get('ds-forgot-email').should('exist');
|
||||||
|
|
||||||
|
// Test accessibility of this page
|
||||||
|
testA11y('ds-forgot-email');
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
// 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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
16
cypress/e2e/metadata-import-page.cy.ts
Normal file
16
cypress/e2e/metadata-import-page.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Metadata Import Page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/metadata-import');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Metadata import form must first be visible
|
||||||
|
cy.get('ds-metadata-import-page').should('be.visible');
|
||||||
|
// Analyze <ds-metadata-import-page> for accessibility issues
|
||||||
|
testA11y('ds-metadata-import-page');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/metadata-registry.cy.ts
Normal file
16
cypress/e2e/metadata-registry.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Metadata Registry', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/registries/metadata');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-metadata-registry').should('be.visible');
|
||||||
|
// Analyze <ds-metadata-registry> for accessibility issues
|
||||||
|
testA11y('ds-metadata-registry');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/metadata-schema.cy.ts
Normal file
16
cypress/e2e/metadata-schema.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Metadata Schema', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/registries/metadata/dc');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-metadata-schema').should('be.visible');
|
||||||
|
// Analyze <ds-metadata-schema> for accessibility issues
|
||||||
|
testA11y('ds-metadata-schema');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,5 +1,3 @@
|
|||||||
import { Options } from 'cypress-axe';
|
|
||||||
import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support/e2e';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('My DSpace page', () => {
|
describe('My DSpace page', () => {
|
||||||
@@ -7,7 +5,7 @@ describe('My DSpace page', () => {
|
|||||||
cy.visit('/mydspace');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
cy.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');
|
||||||
|
|
||||||
@@ -19,28 +17,14 @@ describe('My DSpace page', () => {
|
|||||||
cy.get('.filter-toggle').click({ multiple: true });
|
cy.get('.filter-toggle').click({ multiple: true });
|
||||||
|
|
||||||
// Analyze <ds-my-dspace-page> for accessibility issues
|
// Analyze <ds-my-dspace-page> for accessibility issues
|
||||||
testA11y(
|
testA11y('ds-my-dspace-page');
|
||||||
{
|
|
||||||
include: ['ds-my-dspace-page'],
|
|
||||||
exclude: [
|
|
||||||
['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
// Search filters fail these two "moderate" impact rules
|
|
||||||
'heading-order': { enabled: false },
|
|
||||||
'landmark-unique': { enabled: false }
|
|
||||||
}
|
|
||||||
} as Options
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a working detailed view that passes accessibility tests', () => {
|
it('should have a working detailed view that passes accessibility tests', () => {
|
||||||
cy.visit('/mydspace');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
cy.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');
|
||||||
|
|
||||||
@@ -49,16 +33,8 @@ describe('My DSpace page', () => {
|
|||||||
|
|
||||||
cy.get('ds-object-detail').should('be.visible');
|
cy.get('ds-object-detail').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-search-page> for accessibility issues
|
// Analyze <ds-my-dspace-page> for accessibility issues
|
||||||
testA11y('ds-my-dspace-page',
|
testA11y('ds-my-dspace-page');
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
// Search filters fail these two "moderate" impact rules
|
|
||||||
'heading-order': { enabled: false },
|
|
||||||
'landmark-unique': { enabled: false }
|
|
||||||
}
|
|
||||||
} as Options
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
|
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
|
||||||
@@ -66,7 +42,7 @@ describe('My DSpace page', () => {
|
|||||||
cy.visit('/mydspace');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
||||||
|
|
||||||
// Open the New Submission dropdown
|
// Open the New Submission dropdown
|
||||||
cy.get('button[data-test="submission-dropdown"]').click();
|
cy.get('button[data-test="submission-dropdown"]').click();
|
||||||
@@ -77,10 +53,10 @@ describe('My DSpace page', () => {
|
|||||||
cy.get('ds-create-item-parent-selector').should('be.visible');
|
cy.get('ds-create-item-parent-selector').should('be.visible');
|
||||||
|
|
||||||
// Type in a known Collection name in the search box
|
// Type in a known Collection name in the search box
|
||||||
cy.get('ds-authorized-collection-selector input[type="search"]').type(TEST_SUBMIT_COLLECTION_NAME);
|
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
|
// Click on the button matching that known Collection name
|
||||||
cy.get('ds-authorized-collection-selector button[title="'.concat(TEST_SUBMIT_COLLECTION_NAME).concat('"]')).click();
|
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
|
// New URL should include /workspaceitems, as we've started a new submission
|
||||||
cy.url().should('include', '/workspaceitems');
|
cy.url().should('include', '/workspaceitems');
|
||||||
@@ -89,7 +65,7 @@ describe('My DSpace page', () => {
|
|||||||
cy.get('ds-submission-edit').should('be.visible');
|
cy.get('ds-submission-edit').should('be.visible');
|
||||||
|
|
||||||
// A Collection menu button should exist & its value should be the selected collection
|
// A Collection menu button should exist & its value should be the selected collection
|
||||||
cy.get('#collectionControlsMenuButton span').should('have.text', TEST_SUBMIT_COLLECTION_NAME);
|
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.
|
// 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
|
// Get our Submission URL, to parse out the ID of this new submission
|
||||||
@@ -138,7 +114,7 @@ describe('My DSpace page', () => {
|
|||||||
cy.visit('/mydspace');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
||||||
|
|
||||||
// Open the New Import dropdown
|
// Open the New Import dropdown
|
||||||
cy.get('button[data-test="import-dropdown"]').click();
|
cy.get('button[data-test="import-dropdown"]').click();
|
||||||
@@ -150,6 +126,9 @@ describe('My DSpace page', () => {
|
|||||||
|
|
||||||
// The external import searchbox should be visible
|
// The external import searchbox should be visible
|
||||||
cy.get('ds-submission-import-external-searchbar').should('be.visible');
|
cy.get('ds-submission-import-external-searchbar').should('be.visible');
|
||||||
|
|
||||||
|
// Test for accessibility issues
|
||||||
|
testA11y('ds-submission-import-external');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
16
cypress/e2e/new-process.cy.ts
Normal file
16
cypress/e2e/new-process.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('New Process', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/processes/new');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Process form must first be visible
|
||||||
|
cy.get('ds-new-process').should('be.visible');
|
||||||
|
// Analyze <ds-new-process> for accessibility issues
|
||||||
|
testA11y('ds-new-process');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,8 +1,13 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('PageNotFound', () => {
|
describe('PageNotFound', () => {
|
||||||
it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
|
it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
|
||||||
// request an invalid page (UUIDs at root path aren't valid)
|
// request an invalid page (UUIDs at root path aren't valid)
|
||||||
cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false });
|
cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false });
|
||||||
cy.get('ds-pagenotfound').should('be.visible');
|
cy.get('ds-pagenotfound').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze <ds-pagenotfound> for accessibility issues
|
||||||
|
testA11y('ds-pagenotfound');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not contain element ds-pagenotfound when navigating to existing page', () => {
|
it('should not contain element ds-pagenotfound when navigating to existing page', () => {
|
||||||
|
13
cypress/e2e/privacy.cy.ts
Normal file
13
cypress/e2e/privacy.cy.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Privacy', () => {
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.visit('/info/privacy');
|
||||||
|
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-privacy').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze <ds-privacy> for accessibility
|
||||||
|
testA11y('ds-privacy');
|
||||||
|
});
|
||||||
|
});
|
17
cypress/e2e/processes-overview.cy.ts
Normal file
17
cypress/e2e/processes-overview.cy.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Processes Overview', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/processes');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
|
||||||
|
// Process overview must first be visible
|
||||||
|
cy.get('ds-process-overview').should('be.visible');
|
||||||
|
// Analyze <ds-process-overview> for accessibility issues
|
||||||
|
testA11y('ds-process-overview');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/profile-page.cy.ts
Normal file
16
cypress/e2e/profile-page.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Profile page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/profile');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Process form must first be visible
|
||||||
|
cy.get('ds-profile-page').should('be.visible');
|
||||||
|
// Analyze <ds-profile-page> for accessibility issues
|
||||||
|
testA11y('ds-profile-page');
|
||||||
|
});
|
||||||
|
});
|
16
cypress/e2e/quality-assurance-source-page.cy.ts
Normal file
16
cypress/e2e/quality-assurance-source-page.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Quality Assurance Source Page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/notifications/quality-assurance');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Source page must first be visible
|
||||||
|
cy.get('ds-quality-assurance-source-page-component').should('be.visible');
|
||||||
|
// Analyze <ds-quality-assurance-source-page-component> for accessibility issues
|
||||||
|
testA11y('ds-quality-assurance-source-page-component');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,23 +1,21 @@
|
|||||||
import { TEST_SEARCH_TERM } from 'cypress/support/e2e';
|
|
||||||
|
|
||||||
const page = {
|
const page = {
|
||||||
fillOutQueryInNavBar(query) {
|
fillOutQueryInNavBar(query) {
|
||||||
// Click the magnifying glass
|
// Click the magnifying glass
|
||||||
cy.get('ds-themed-navbar [data-test="header-search-icon"]').click();
|
cy.get('ds-header [data-test="header-search-icon"]').click();
|
||||||
// Fill out a query in input that appears
|
// Fill out a query in input that appears
|
||||||
cy.get('ds-themed-navbar [data-test="header-search-box"]').type(query);
|
cy.get('ds-header [data-test="header-search-box"]').type(query);
|
||||||
},
|
},
|
||||||
submitQueryByPressingEnter() {
|
submitQueryByPressingEnter() {
|
||||||
cy.get('ds-themed-navbar [data-test="header-search-box"]').type('{enter}');
|
cy.get('ds-header [data-test="header-search-box"]').type('{enter}');
|
||||||
},
|
},
|
||||||
submitQueryByPressingIcon() {
|
submitQueryByPressingIcon() {
|
||||||
cy.get('ds-themed-navbar [data-test="header-search-icon"]').click();
|
cy.get('ds-header [data-test="header-search-icon"]').click();
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Search from Navigation Bar', () => {
|
describe('Search from Navigation Bar', () => {
|
||||||
// NOTE: these tests currently assume this query will return results!
|
// NOTE: these tests currently assume this query will return results!
|
||||||
const query = TEST_SEARCH_TERM;
|
const query = Cypress.env('DSPACE_TEST_SEARCH_TERM');
|
||||||
|
|
||||||
it('should go to search page with correct query if submitted (from home)', () => {
|
it('should go to search page with correct query if submitted (from home)', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import { Options } from 'cypress-axe';
|
|
||||||
import { TEST_SEARCH_TERM } from 'cypress/support/e2e';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
import { Options } from 'cypress-axe';
|
||||||
|
|
||||||
describe('Search Page', () => {
|
describe('Search Page', () => {
|
||||||
|
// 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', () => {
|
it('should redirect to the correct url when query was set and submit button was triggered', () => {
|
||||||
const queryString = 'Another interesting query string';
|
const queryString = 'Another interesting query string';
|
||||||
cy.visit('/search');
|
cy.visit('/search');
|
||||||
@@ -13,8 +15,8 @@ describe('Search Page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should load results and pass accessibility tests', () => {
|
it('should load results and pass accessibility tests', () => {
|
||||||
cy.visit('/search?query='.concat(TEST_SEARCH_TERM));
|
cy.visit('/search?query='.concat(query));
|
||||||
cy.get('[data-test="search-box"]').should('have.value', TEST_SEARCH_TERM);
|
cy.get('[data-test="search-box"]').should('have.value', query);
|
||||||
|
|
||||||
// <ds-search-page> tag must be loaded
|
// <ds-search-page> tag must be loaded
|
||||||
cy.get('ds-search-page').should('be.visible');
|
cy.get('ds-search-page').should('be.visible');
|
||||||
@@ -27,25 +29,11 @@ describe('Search Page', () => {
|
|||||||
cy.get('[data-test="filter-toggle"]').click({ multiple: true });
|
cy.get('[data-test="filter-toggle"]').click({ multiple: true });
|
||||||
|
|
||||||
// Analyze <ds-search-page> for accessibility issues
|
// Analyze <ds-search-page> for accessibility issues
|
||||||
testA11y(
|
testA11y('ds-search-page');
|
||||||
{
|
|
||||||
include: ['ds-search-page'],
|
|
||||||
exclude: [
|
|
||||||
['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
// Search filters fail these two "moderate" impact rules
|
|
||||||
'heading-order': { enabled: false },
|
|
||||||
'landmark-unique': { enabled: false }
|
|
||||||
}
|
|
||||||
} as Options
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a working grid view that passes accessibility tests', () => {
|
it('should have a working grid view that passes accessibility tests', () => {
|
||||||
cy.visit('/search?query='.concat(TEST_SEARCH_TERM));
|
cy.visit('/search?query='.concat(query));
|
||||||
|
|
||||||
// Click button in sidebar to display grid view
|
// Click button in sidebar to display grid view
|
||||||
cy.get('ds-search-sidebar [data-test="grid-view"]').click();
|
cy.get('ds-search-sidebar [data-test="grid-view"]').click();
|
||||||
@@ -60,11 +48,10 @@ describe('Search Page', () => {
|
|||||||
testA11y('ds-search-page',
|
testA11y('ds-search-page',
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
// Search filters fail these two "moderate" impact rules
|
// Card titles fail this test currently
|
||||||
'heading-order': { enabled: false },
|
'heading-order': { enabled: false },
|
||||||
'landmark-unique': { enabled: false }
|
},
|
||||||
}
|
} as Options,
|
||||||
} as Options
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support/e2e';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
//import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID, TEST_ADMIN_USER, TEST_ADMIN_PASSWORD } from 'cypress/support/e2e';
|
||||||
|
import { Options } from 'cypress-axe';
|
||||||
|
|
||||||
describe('New Submission page', () => {
|
describe('New Submission page', () => {
|
||||||
// NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts
|
|
||||||
|
|
||||||
|
// 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', () => {
|
it('should create a new submission when using /submit path & pass accessibility', () => {
|
||||||
// Test that calling /submit with collection & entityType will create a new submission
|
// Test that calling /submit with collection & entityType will create a new submission
|
||||||
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
|
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.
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
cy.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
|
// Should redirect to /workspaceitems, as we've started a new submission
|
||||||
cy.url().should('include', '/workspaceitems');
|
cy.url().should('include', '/workspaceitems');
|
||||||
@@ -17,7 +19,7 @@ describe('New Submission page', () => {
|
|||||||
cy.get('ds-submission-edit').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
|
// A Collection menu button should exist & it's value should be the selected collection
|
||||||
cy.get('#collectionControlsMenuButton span').should('have.text', TEST_SUBMIT_COLLECTION_NAME);
|
cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME'));
|
||||||
|
|
||||||
// 4 sections should be visible by default
|
// 4 sections should be visible by default
|
||||||
cy.get('div#section_traditionalpageone').should('be.visible');
|
cy.get('div#section_traditionalpageone').should('be.visible');
|
||||||
@@ -25,6 +27,25 @@ describe('New Submission page', () => {
|
|||||||
cy.get('div#section_upload').should('be.visible');
|
cy.get('div#section_upload').should('be.visible');
|
||||||
cy.get('div#section_license').should('be.visible');
|
cy.get('div#section_license').should('be.visible');
|
||||||
|
|
||||||
|
// 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
|
// Discard button should work
|
||||||
// Clicking it will display a confirmation, which we will confirm with another click
|
// Clicking it will display a confirmation, which we will confirm with another click
|
||||||
cy.get('button#discard').click();
|
cy.get('button#discard').click();
|
||||||
@@ -33,10 +54,10 @@ describe('New Submission page', () => {
|
|||||||
|
|
||||||
it('should block submission & show errors if required fields are missing', () => {
|
it('should block submission & show errors if required fields are missing', () => {
|
||||||
// Create a new submission
|
// Create a new submission
|
||||||
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
|
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.
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
||||||
|
|
||||||
// Attempt an immediate deposit without filling out any fields
|
// Attempt an immediate deposit without filling out any fields
|
||||||
cy.get('button#deposit').click();
|
cy.get('button#deposit').click();
|
||||||
@@ -93,10 +114,10 @@ describe('New Submission page', () => {
|
|||||||
|
|
||||||
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
||||||
// Create a new submission
|
// Create a new submission
|
||||||
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
|
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.
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
||||||
|
|
||||||
// Fill out all required fields (Title, Date)
|
// Fill out all required fields (Title, Date)
|
||||||
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
|
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
|
||||||
@@ -116,8 +137,8 @@ describe('New Submission page', () => {
|
|||||||
|
|
||||||
// Upload our DSpace logo via drag & drop onto submission form
|
// Upload our DSpace logo via drag & drop onto submission form
|
||||||
// cy.get('div#section_upload')
|
// cy.get('div#section_upload')
|
||||||
cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.png', {
|
cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.svg', {
|
||||||
action: 'drag-drop'
|
action: 'drag-drop',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for upload to complete before proceeding
|
// Wait for upload to complete before proceeding
|
||||||
@@ -131,4 +152,76 @@ describe('New Submission page', () => {
|
|||||||
cy.get('ds-notification div.alert-success').should('be.visible');
|
cy.get('ds-notification div.alert-success').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
// 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'));
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// This should display the <ds-create-item-parent-selector> (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_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',
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
16
cypress/e2e/system-wide-alert.cy.ts
Normal file
16
cypress/e2e/system-wide-alert.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('System Wide Alert', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Must login as an Admin to see the page
|
||||||
|
cy.visit('/admin/system-wide-alert');
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
// Page must first be visible
|
||||||
|
cy.get('ds-system-wide-alert-form').should('be.visible');
|
||||||
|
// Analyze <ds-system-wide-alert-form> for accessibility issues
|
||||||
|
testA11y('ds-system-wide-alert-form');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,5 +1,11 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// These two global variables are used to store information about the REST API used
|
||||||
|
// by these e2e tests. They are filled out prior to running any tests in the before()
|
||||||
|
// method of e2e.ts. They can then be accessed by any tests via the getters below.
|
||||||
|
let REST_BASE_URL: string;
|
||||||
|
let REST_DOMAIN: string;
|
||||||
|
|
||||||
// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress
|
// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress
|
||||||
// For more info, visit https://on.cypress.io/plugins-api
|
// For more info, visit https://on.cypress.io/plugins-api
|
||||||
module.exports = (on, config) => {
|
module.exports = (on, config) => {
|
||||||
@@ -30,6 +36,24 @@ module.exports = (on, config) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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 ;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -3,13 +3,15 @@
|
|||||||
// See docs at https://docs.cypress.io/api/cypress-api/custom-commands
|
// 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 {
|
||||||
import { DSPACE_XSRF_COOKIE, XSRF_REQUEST_HEADER } from 'src/app/core/xsrf/xsrf.constants';
|
AuthTokenInfo,
|
||||||
|
TOKENITEM,
|
||||||
// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL
|
} from 'src/app/core/auth/models/auth-token-info.model';
|
||||||
// from the Angular UI's config.json. See 'login()'.
|
import {
|
||||||
export const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server';
|
DSPACE_XSRF_COOKIE,
|
||||||
export const FALLBACK_TEST_REST_DOMAIN = 'localhost';
|
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
|
// Declare Cypress namespace to help with Intellisense & code completion in IDEs
|
||||||
// ALL custom commands MUST be listed here for code completion to work
|
// ALL custom commands MUST be listed here for code completion to work
|
||||||
@@ -41,6 +43,13 @@ declare global {
|
|||||||
* @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
|
* @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
|
||||||
*/
|
*/
|
||||||
generateViewEvent(uuid: string, dsoType: string): typeof generateViewEvent;
|
generateViewEvent(uuid: string, dsoType: string): typeof generateViewEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new CSRF token and add to required Cookie. CSRF Token is returned
|
||||||
|
* in chainable in order to allow it to be sent also in required CSRF header.
|
||||||
|
* @returns Chainable reference to allow CSRF token to also be sent in header.
|
||||||
|
*/
|
||||||
|
createCSRFCookie(): Chainable<any>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,42 +63,17 @@ declare global {
|
|||||||
* @param password password to login as
|
* @param password password to login as
|
||||||
*/
|
*/
|
||||||
function login(email: string, password: string): void {
|
function login(email: string, password: string): void {
|
||||||
// Cypress doesn't have access to the running application in Node.js.
|
// Create a fake CSRF cookie/token to use in POST
|
||||||
// So, it's not possible to inject or load the AppConfig or environment of the Angular UI.
|
cy.createCSRFCookie().then((csrfToken: string) => {
|
||||||
// Instead, we'll read our running application's config.json, which contains the configs &
|
// get our REST API's base URL, also needed for POST
|
||||||
// is regenerated at runtime each time the Angular UI application starts up.
|
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
|
||||||
cy.task('readUIConfig').then((str: string) => {
|
|
||||||
// Parse config into a JSON object
|
|
||||||
const config = JSON.parse(str);
|
|
||||||
|
|
||||||
// Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found.
|
|
||||||
let baseRestUrl = FALLBACK_TEST_REST_BASE_URL;
|
|
||||||
if (!config.rest.baseUrl) {
|
|
||||||
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
|
|
||||||
} else {
|
|
||||||
//console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: ".concat(config.rest.baseUrl));
|
|
||||||
baseRestUrl = config.rest.baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now find domain of our REST API, again with a fallback.
|
|
||||||
let baseDomain = FALLBACK_TEST_REST_DOMAIN;
|
|
||||||
if (!config.rest.host) {
|
|
||||||
console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN);
|
|
||||||
} else {
|
|
||||||
baseDomain = config.rest.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a fake CSRF Token. Set it in the required server-side cookie
|
|
||||||
const csrfToken = 'fakeLoginCSRFToken';
|
|
||||||
cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain });
|
|
||||||
|
|
||||||
// Now, send login POST request including that CSRF token
|
// Now, send login POST request including that CSRF token
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: baseRestUrl + '/api/authn/login',
|
url: baseRestUrl + '/api/authn/login',
|
||||||
headers: { [XSRF_REQUEST_HEADER]: csrfToken },
|
headers: { [XSRF_REQUEST_HEADER]: csrfToken },
|
||||||
form: true, // indicates the body should be form urlencoded
|
form: true, // indicates the body should be form urlencoded
|
||||||
body: { user: email, password: password }
|
body: { user: email, password: password },
|
||||||
}).then((resp) => {
|
}).then((resp) => {
|
||||||
// We expect a successful login
|
// We expect a successful login
|
||||||
expect(resp.status).to.eq(200);
|
expect(resp.status).to.eq(200);
|
||||||
@@ -104,9 +88,7 @@ function login(email: string, password: string): void {
|
|||||||
// This ensures the UI will recognize we are logged in on next "visit()"
|
// This ensures the UI will recognize we are logged in on next "visit()"
|
||||||
cy.setCookie(TOKENITEM, JSON.stringify(authinfo));
|
cy.setCookie(TOKENITEM, JSON.stringify(authinfo));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
// Remove cookie with fake CSRF token, as it's no longer needed
|
|
||||||
cy.clearCookie(DSPACE_XSRF_COOKIE);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Add as a Cypress command (i.e. assign to 'cy.login')
|
// Add as a Cypress command (i.e. assign to 'cy.login')
|
||||||
@@ -119,11 +101,11 @@ Cypress.Commands.add('login', login);
|
|||||||
*/
|
*/
|
||||||
function loginViaForm(email: string, password: string): void {
|
function loginViaForm(email: string, password: string): void {
|
||||||
// Enter email
|
// Enter email
|
||||||
cy.get('ds-log-in [data-test="email"]').type(email);
|
cy.get('[data-test="email"]').type(email);
|
||||||
// Enter password
|
// Enter password
|
||||||
cy.get('ds-log-in [data-test="password"]').type(password);
|
cy.get('[data-test="password"]').type(password);
|
||||||
// Click login button
|
// Click login button
|
||||||
cy.get('ds-log-in [data-test="login-button"]').click();
|
cy.get('[data-test="login-button"]').click();
|
||||||
}
|
}
|
||||||
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
|
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
|
||||||
Cypress.Commands.add('loginViaForm', loginViaForm);
|
Cypress.Commands.add('loginViaForm', loginViaForm);
|
||||||
@@ -141,34 +123,10 @@ Cypress.Commands.add('loginViaForm', loginViaForm);
|
|||||||
* @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
|
* @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
|
||||||
*/
|
*/
|
||||||
function generateViewEvent(uuid: string, dsoType: string): void {
|
function generateViewEvent(uuid: string, dsoType: string): void {
|
||||||
// Cypress doesn't have access to the running application in Node.js.
|
// Create a fake CSRF cookie/token to use in POST
|
||||||
// So, it's not possible to inject or load the AppConfig or environment of the Angular UI.
|
cy.createCSRFCookie().then((csrfToken: string) => {
|
||||||
// Instead, we'll read our running application's config.json, which contains the configs &
|
// get our REST API's base URL, also needed for POST
|
||||||
// is regenerated at runtime each time the Angular UI application starts up.
|
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
|
||||||
cy.task('readUIConfig').then((str: string) => {
|
|
||||||
// Parse config into a JSON object
|
|
||||||
const config = JSON.parse(str);
|
|
||||||
|
|
||||||
// Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found.
|
|
||||||
let baseRestUrl = FALLBACK_TEST_REST_BASE_URL;
|
|
||||||
if (!config.rest.baseUrl) {
|
|
||||||
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
|
|
||||||
} else {
|
|
||||||
baseRestUrl = config.rest.baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now find domain of our REST API, again with a fallback.
|
|
||||||
let baseDomain = FALLBACK_TEST_REST_DOMAIN;
|
|
||||||
if (!config.rest.host) {
|
|
||||||
console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN);
|
|
||||||
} else {
|
|
||||||
baseDomain = config.rest.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a fake CSRF Token. Set it in the required server-side cookie
|
|
||||||
const csrfToken = 'fakeGenerateViewEventCSRFToken';
|
|
||||||
cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain });
|
|
||||||
|
|
||||||
// Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header
|
// Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -177,6 +135,8 @@ function generateViewEvent(uuid: string, dsoType: string): void {
|
|||||||
[XSRF_REQUEST_HEADER] : csrfToken,
|
[XSRF_REQUEST_HEADER] : csrfToken,
|
||||||
// use a known public IP address to avoid being seen as a "bot"
|
// use a known public IP address to avoid being seen as a "bot"
|
||||||
'X-Forwarded-For': '1.1.1.1',
|
'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
|
//form: true, // indicates the body should be form urlencoded
|
||||||
body: { targetId: uuid, targetType: dsoType },
|
body: { targetId: uuid, targetType: dsoType },
|
||||||
@@ -184,11 +144,32 @@ function generateViewEvent(uuid: string, dsoType: string): void {
|
|||||||
// We expect a 201 (which means statistics event was created)
|
// We expect a 201 (which means statistics event was created)
|
||||||
expect(resp.status).to.eq(201);
|
expect(resp.status).to.eq(201);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
// Remove cookie with fake CSRF token, as it's no longer needed
|
|
||||||
cy.clearCookie(DSPACE_XSRF_COOKIE);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Add as a Cypress command (i.e. assign to 'cy.generateViewEvent')
|
// Add as a Cypress command (i.e. assign to 'cy.generateViewEvent')
|
||||||
Cypress.Commands.add('generateViewEvent', generateViewEvent);
|
Cypress.Commands.add('generateViewEvent', generateViewEvent);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used by tests to generate a random XSRF/CSRF token and save it to
|
||||||
|
* the required XSRF/CSRF cookie for usage when sending POST requests or similar.
|
||||||
|
* The generated CSRF token is returned in a Chainable to allow it to be also sent
|
||||||
|
* in the CSRF HTTP Header.
|
||||||
|
* @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();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
// Add as a Cypress command (i.e. assign to 'cy.createCSRFCookie')
|
||||||
|
Cypress.Commands.add('createCSRFCookie', createCSRFCookie);
|
||||||
|
@@ -15,49 +15,57 @@
|
|||||||
|
|
||||||
// Import all custom Commands (from commands.ts) for all tests
|
// Import all custom Commands (from commands.ts) for all tests
|
||||||
import './commands';
|
import './commands';
|
||||||
|
|
||||||
// Import Cypress Axe tools for all tests
|
// Import Cypress Axe tools for all tests
|
||||||
// https://github.com/component-driven/cypress-axe
|
// https://github.com/component-driven/cypress-axe
|
||||||
import 'cypress-axe';
|
import 'cypress-axe';
|
||||||
|
|
||||||
|
import { DSPACE_XSRF_COOKIE } from 'src/app/core/xsrf/xsrf.constants';
|
||||||
|
|
||||||
|
// Runs once before all tests
|
||||||
|
before(() => {
|
||||||
|
// Cypress doesn't have access to the running application in Node.js.
|
||||||
|
// So, it's not possible to inject or load the AppConfig or environment of the Angular UI.
|
||||||
|
// Instead, we'll read our running application's config.json, which contains the configs &
|
||||||
|
// is regenerated at runtime each time the Angular UI application starts up.
|
||||||
|
cy.task('readUIConfig').then((str: string) => {
|
||||||
|
// Parse config into a JSON object
|
||||||
|
const config = JSON.parse(str);
|
||||||
|
|
||||||
|
// Find 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);
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
baseDomain = config.rest.host;
|
||||||
|
}
|
||||||
|
cy.task('saveRestBaseDomain', baseDomain);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Runs once before the first test in each "block"
|
// Runs once before the first test in each "block"
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie
|
// 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.
|
// 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}');
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
// For better stability between tests, we visit "about:blank" (i.e. blank page) after each test.
|
// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL
|
||||||
// This ensures any remaining/outstanding XHR requests are killed, so they don't affect the next test.
|
// from the Angular UI's config.json. See 'before()' above.
|
||||||
// Borrowed from: https://glebbahmutov.com/blog/visit-blank-page-between-tests/
|
const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server';
|
||||||
/*afterEach(() => {
|
const FALLBACK_TEST_REST_DOMAIN = 'localhost';
|
||||||
cy.window().then((win) => {
|
|
||||||
win.location.href = 'about:blank';
|
|
||||||
});
|
|
||||||
});*/
|
|
||||||
|
|
||||||
|
|
||||||
// Global constants used in tests
|
|
||||||
// May be overridden in our cypress.json config file using specified environment variables.
|
|
||||||
// Default values listed here are all valid for the Demo Entities Data set available at
|
|
||||||
// https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
|
||||||
// (This is the data set used in our CI environment)
|
|
||||||
|
|
||||||
// Admin account used for administrative tests
|
|
||||||
export const TEST_ADMIN_USER = Cypress.env('DSPACE_TEST_ADMIN_USER') || 'dspacedemo+admin@gmail.com';
|
|
||||||
export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace';
|
|
||||||
// Community/collection/publication used for view/edit tests
|
|
||||||
export const TEST_COLLECTION = Cypress.env('DSPACE_TEST_COLLECTION') || '282164f5-d325-4740-8dd1-fa4d6d3e7200';
|
|
||||||
export const TEST_COMMUNITY = Cypress.env('DSPACE_TEST_COMMUNITY') || '0958c910-2037-42a9-81c7-dca80e3892b4';
|
|
||||||
export const TEST_ENTITY_PUBLICATION = Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION') || 'e98b0f27-5c19-49a0-960d-eb6ad5287067';
|
|
||||||
// Search term (should return results) used in search tests
|
|
||||||
export const TEST_SEARCH_TERM = Cypress.env('DSPACE_TEST_SEARCH_TERM') || 'test';
|
|
||||||
// Collection used for submission tests
|
|
||||||
export const TEST_SUBMIT_COLLECTION_NAME = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME') || 'Sample Collection';
|
|
||||||
export const TEST_SUBMIT_COLLECTION_UUID = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID') || '9d8334e9-25d3-4a67-9cea-3dffdef80144';
|
|
||||||
export const TEST_SUBMIT_USER = Cypress.env('DSPACE_TEST_SUBMIT_USER') || 'dspacedemo+submit@gmail.com';
|
|
||||||
export const TEST_SUBMIT_USER_PASSWORD = Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD') || 'dspace';
|
|
||||||
|
|
||||||
|
|
||||||
// USEFUL REGEX for testing
|
// USEFUL REGEX for testing
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ import { Options } from 'cypress-axe';
|
|||||||
function terminalLog(violations: Result[]) {
|
function terminalLog(violations: Result[]) {
|
||||||
cy.task(
|
cy.task(
|
||||||
'log',
|
'log',
|
||||||
`${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${violations.length === 1 ? 'was' : 'were'} detected`
|
`${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${violations.length === 1 ? 'was' : 'were'} detected`,
|
||||||
);
|
);
|
||||||
// pluck specific keys to keep the table readable
|
// pluck specific keys to keep the table readable
|
||||||
const violationData = violations.map(
|
const violationData = violations.map(
|
||||||
@@ -17,8 +17,8 @@ function terminalLog(violations: Result[]) {
|
|||||||
description,
|
description,
|
||||||
helpUrl,
|
helpUrl,
|
||||||
nodes: nodes.length,
|
nodes: nodes.length,
|
||||||
html: nodes.map(node => node.html)
|
html: nodes.map(node => node.html),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Print violations as an array, since 'node.html' above often breaks table alignment
|
// Print violations as an array, since 'node.html' above often breaks table alignment
|
||||||
@@ -38,7 +38,7 @@ export const testA11y = (context?: any, options?: Options) => {
|
|||||||
// Disable color contrast checks as they are inaccurate / result in a lot of false positives
|
// 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
|
// See also open issues in axe-core: https://github.com/dequelabs/axe-core/labels/color%20contrast
|
||||||
{ id: 'color-contrast', enabled: false },
|
{ id: 'color-contrast', enabled: false },
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
cy.checkA11y(context, options, terminalLog);
|
cy.checkA11y(context, options, terminalLog);
|
||||||
};
|
};
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
"**/*.ts"
|
"**/*.ts"
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"sourceMap": false,
|
||||||
"types": [
|
"types": [
|
||||||
"cypress",
|
"cypress",
|
||||||
"cypress-axe",
|
"cypress-axe",
|
||||||
|
@@ -20,17 +20,17 @@ the Docker compose scripts in this 'docker' folder.
|
|||||||
|
|
||||||
### Dockerfile
|
### Dockerfile
|
||||||
|
|
||||||
This Dockerfile is used to build a *development* DSpace 7 Angular UI image, published as 'dspace/dspace-angular'
|
This Dockerfile is used to build a *development* DSpace Angular UI image, published as 'dspace/dspace-angular'
|
||||||
|
|
||||||
```
|
```
|
||||||
docker build -t dspace/dspace-angular:dspace-7_x .
|
docker build -t dspace/dspace-angular:dspace-8_x .
|
||||||
```
|
```
|
||||||
|
|
||||||
This image is built *automatically* after each commit is made to the `main` branch.
|
This image is built *automatically* after each commit is made to the `main` branch.
|
||||||
|
|
||||||
Admins to our DockerHub repo can manually publish with the following command.
|
Admins to our DockerHub repo can manually publish with the following command.
|
||||||
```
|
```
|
||||||
docker push dspace/dspace-angular:dspace-7_x
|
docker push dspace/dspace-angular:dspace-8_x
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dockerfile.dist
|
### Dockerfile.dist
|
||||||
@@ -39,18 +39,18 @@ The `Dockerfile.dist` is used to generate a *production* build and runtime envir
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# build the latest image
|
# build the latest image
|
||||||
docker build -f Dockerfile.dist -t dspace/dspace-angular:dspace-7_x-dist .
|
docker build -f Dockerfile.dist -t dspace/dspace-angular:dspace-8_x-dist .
|
||||||
```
|
```
|
||||||
|
|
||||||
A default/demo version of this image is built *automatically*.
|
A default/demo version of this image is built *automatically*.
|
||||||
|
|
||||||
## 'docker' directory
|
## 'docker' directory
|
||||||
- docker-compose.yml
|
- docker-compose.yml
|
||||||
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.
|
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace REST instance will also be started in Docker.
|
||||||
- docker-compose-rest.yml
|
- docker-compose-rest.yml
|
||||||
- Runs a published instance of the DSpace 7 REST API - persists data in Docker volumes
|
- Runs a published instance of the DSpace REST API - persists data in Docker volumes
|
||||||
- docker-compose-ci.yml
|
- docker-compose-ci.yml
|
||||||
- Runs a published instance of the DSpace 7 REST API for CI testing. The database is re-populated from a SQL dump on each startup.
|
- Runs a published instance of the DSpace REST API for CI testing. The database is re-populated from a SQL dump on each startup.
|
||||||
- cli.yml
|
- cli.yml
|
||||||
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
|
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
|
||||||
- cli.assetstore.yml
|
- cli.assetstore.yml
|
||||||
@@ -59,19 +59,19 @@ A default/demo version of this image is built *automatically*.
|
|||||||
|
|
||||||
## To refresh / pull DSpace images from Dockerhub
|
## To refresh / pull DSpace images from Dockerhub
|
||||||
```
|
```
|
||||||
docker-compose -f docker/docker-compose.yml pull
|
docker compose -f docker/docker-compose.yml pull
|
||||||
```
|
```
|
||||||
|
|
||||||
## To build DSpace images using code in your branch
|
## To build DSpace images using code in your branch
|
||||||
```
|
```
|
||||||
docker-compose -f docker/docker-compose.yml build
|
docker compose -f docker/docker-compose.yml build
|
||||||
```
|
```
|
||||||
|
|
||||||
## To start DSpace (REST and Angular) from your branch
|
## To start DSpace (REST and Angular) from your branch
|
||||||
|
|
||||||
This command provides a quick way to start both the frontend & backend from this single codebase
|
This command provides a quick way to start both the frontend & backend from this single codebase
|
||||||
```
|
```
|
||||||
docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d
|
docker compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Keep in mind, you may also start the backend by cloning the 'DSpace/DSpace' GitHub repository separately. See the next section.
|
Keep in mind, you may also start the backend by cloning the 'DSpace/DSpace' GitHub repository separately. See the next section.
|
||||||
@@ -86,14 +86,14 @@ _The system will be started in 2 steps. Each step shares the same docker network
|
|||||||
|
|
||||||
From 'DSpace/DSpace' clone (build first as needed):
|
From 'DSpace/DSpace' clone (build first as needed):
|
||||||
```
|
```
|
||||||
docker-compose -p d7 up -d
|
docker compose -p d8 up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
NOTE: More detailed instructions on starting the backend via Docker can be found in the [Docker Compose instructions for the Backend](https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/README.md).
|
NOTE: More detailed instructions on starting the backend via Docker can be found in the [Docker Compose instructions for the Backend](https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/README.md).
|
||||||
|
|
||||||
From 'DSpace/dspace-angular' clone (build first as needed)
|
From 'DSpace/dspace-angular' clone (build first as needed)
|
||||||
```
|
```
|
||||||
docker-compose -p d7 -f docker/docker-compose.yml up -d
|
docker compose -p d8 -f docker/docker-compose.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
At this point, you should be able to access the UI from http://localhost:4000,
|
At this point, you should be able to access the UI from http://localhost:4000,
|
||||||
@@ -101,25 +101,25 @@ and the backend at http://localhost:8080/server/
|
|||||||
|
|
||||||
## Run DSpace Angular dist build with DSpace Demo site backend
|
## Run DSpace Angular dist build with DSpace Demo site backend
|
||||||
|
|
||||||
This allows you to run the Angular UI in *production* mode, pointing it at the demo backend
|
This allows you to run the Angular UI in *production* mode, pointing it at the demo or sandbox backend
|
||||||
(https://api7.dspace.org/server/).
|
(https://demo.dspace.org/server/ or https://sandbox.dspace.org/server/).
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose -f docker/docker-compose-dist.yml pull
|
docker compose -f docker/docker-compose-dist.yml pull
|
||||||
docker-compose -f docker/docker-compose-dist.yml build
|
docker compose -f docker/docker-compose-dist.yml build
|
||||||
docker-compose -p d7 -f docker/docker-compose-dist.yml up -d
|
docker compose -p d8 -f docker/docker-compose-dist.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ingest test data from AIPDIR
|
## Ingest test data from AIPDIR
|
||||||
|
|
||||||
Create an administrator
|
Create an administrator
|
||||||
```
|
```
|
||||||
docker-compose -p d7 -f docker/cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en
|
docker compose -p d8 -f docker/cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en
|
||||||
```
|
```
|
||||||
|
|
||||||
Load content from AIP files
|
Load content from AIP files
|
||||||
```
|
```
|
||||||
docker-compose -p d7 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli
|
docker compose -p d8 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
## Alternative Ingest - Use Entities dataset
|
## Alternative Ingest - Use Entities dataset
|
||||||
@@ -127,12 +127,12 @@ _Delete your docker volumes or use a unique project (-p) name_
|
|||||||
|
|
||||||
Start DSpace with Database Content from a database dump
|
Start DSpace with Database Content from a database dump
|
||||||
```
|
```
|
||||||
docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d
|
docker compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Load assetstore content and trigger a re-index of the repository
|
Load assetstore content and trigger a re-index of the repository
|
||||||
```
|
```
|
||||||
docker-compose -p d7 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli
|
docker compose -p d8 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
## End to end testing of the REST API (runs in GitHub Actions CI).
|
## End to end testing of the REST API (runs in GitHub Actions CI).
|
||||||
@@ -140,5 +140,5 @@ _In this instance, only the REST api runs in Docker using the Entities dataset.
|
|||||||
|
|
||||||
This command is only really useful for testing our Continuous Integration process.
|
This command is only really useful for testing our Continuous Integration process.
|
||||||
```
|
```
|
||||||
docker-compose -p d7ci -f docker/docker-compose-ci.yml up -d
|
docker compose -p d8ci -f docker/docker-compose-ci.yml up -d
|
||||||
```
|
```
|
||||||
|
@@ -12,15 +12,8 @@
|
|||||||
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.assetstore.yml
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.assetstore.yml
|
||||||
#
|
#
|
||||||
# Therefore, it should be kept in sync with that file
|
# Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
|
||||||
|
|
||||||
networks:
|
|
||||||
dspacenet:
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dspace-cli:
|
dspace-cli:
|
||||||
networks:
|
|
||||||
dspacenet: {}
|
|
||||||
environment:
|
environment:
|
||||||
# This assetstore zip is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
# This assetstore zip is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
||||||
- LOADASSETS=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz
|
- LOADASSETS=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz
|
||||||
|
@@ -12,8 +12,6 @@
|
|||||||
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.ingest.yml
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.ingest.yml
|
||||||
#
|
#
|
||||||
# Therefore, it should be kept in sync with that file
|
# Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dspace-cli:
|
dspace-cli:
|
||||||
environment:
|
environment:
|
||||||
@@ -34,5 +32,7 @@ services:
|
|||||||
|
|
||||||
/dspace/bin/dspace packager -r -a -t AIP -e $${ADMIN_EMAIL} -f -u SITE*.zip
|
/dspace/bin/dspace packager -r -a -t AIP -e $${ADMIN_EMAIL} -f -u SITE*.zip
|
||||||
/dspace/bin/dspace database update-sequences
|
/dspace/bin/dspace database update-sequences
|
||||||
|
touch /dspace/solr/search/conf/reindex.flag
|
||||||
|
|
||||||
/dspace/bin/dspace index-discovery
|
/dspace/bin/dspace oai import
|
||||||
|
/dspace/bin/dspace oai clean-cache
|
||||||
|
@@ -12,11 +12,16 @@
|
|||||||
# https://github.com/DSpace/DSpace/blob/main/docker-compose-cli.yml
|
# https://github.com/DSpace/DSpace/blob/main/docker-compose-cli.yml
|
||||||
#
|
#
|
||||||
# Therefore, it should be kept in sync with that file
|
# Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
networks:
|
||||||
|
# Default to using network named 'dspacenet' from docker-compose-rest.yml.
|
||||||
|
# Its full name will be prepended with the project name (e.g. "-p d8" means it will be named "d8_dspacenet")
|
||||||
|
# If COMPOSITE_PROJECT_NAME is missing, default value will be "docker" (name of folder this file is in)
|
||||||
|
default:
|
||||||
|
name: ${COMPOSE_PROJECT_NAME:-docker}_dspacenet
|
||||||
|
external: true
|
||||||
services:
|
services:
|
||||||
dspace-cli:
|
dspace-cli:
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-8_x}"
|
||||||
container_name: dspace-cli
|
container_name: dspace-cli
|
||||||
environment:
|
environment:
|
||||||
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||||
@@ -30,16 +35,12 @@ services:
|
|||||||
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
solr__P__server: http://dspacesolr:8983/solr
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
volumes:
|
volumes:
|
||||||
- "assetstore:/dspace/assetstore"
|
# Keep DSpace assetstore directory between reboots
|
||||||
|
- assetstore:/dspace/assetstore
|
||||||
entrypoint: /dspace/bin/dspace
|
entrypoint: /dspace/bin/dspace
|
||||||
command: help
|
command: help
|
||||||
networks:
|
|
||||||
- dspacenet
|
|
||||||
tty: true
|
tty: true
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
|
|
||||||
networks:
|
|
||||||
dspacenet:
|
|
||||||
|
@@ -12,14 +12,13 @@
|
|||||||
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
|
||||||
#
|
#
|
||||||
# # Therefore, it should be kept in sync with that file
|
# # Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dspacedb:
|
dspacedb:
|
||||||
image: dspace/dspace-postgres-pgcrypto:loadsql
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-8_x}-loadsql"
|
||||||
environment:
|
environment:
|
||||||
# This LOADSQL should be kept in sync with the URL in DSpace/DSpace
|
# This LOADSQL should be kept in sync with the URL in DSpace/DSpace
|
||||||
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
||||||
|
# NOTE: currently there is no dspace8 version
|
||||||
- LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
|
- LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
|
||||||
dspace:
|
dspace:
|
||||||
### OVERRIDE default 'entrypoint' in 'docker-compose-rest.yml' ####
|
### OVERRIDE default 'entrypoint' in 'docker-compose-rest.yml' ####
|
||||||
@@ -29,23 +28,11 @@ services:
|
|||||||
# 3. (Custom for Entities) enable Entity-specific collection submission mappings in item-submission.xml
|
# 3. (Custom for Entities) enable Entity-specific collection submission mappings in item-submission.xml
|
||||||
# This 'sed' command inserts the sample configurations specific to the Entities data set, see:
|
# This 'sed' command inserts the sample configurations specific to the Entities data set, see:
|
||||||
# https://github.com/DSpace/DSpace/blob/main/dspace/config/item-submission.xml#L36-L49
|
# https://github.com/DSpace/DSpace/blob/main/dspace/config/item-submission.xml#L36-L49
|
||||||
# 4. Finally, start Tomcat
|
# 4. Finally, start DSpace
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '-c'
|
- '-c'
|
||||||
- |
|
- |
|
||||||
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
|
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
|
||||||
/dspace/bin/dspace database migrate ignored
|
/dspace/bin/dspace database migrate ignored
|
||||||
sed -i '/name-map collection-handle="default".*/a \\n <name-map collection-handle="123456789/3" submission-name="Publication"/> \
|
java -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace
|
||||||
<name-map collection-handle="123456789/4" submission-name="Publication"/> \
|
|
||||||
<name-map collection-handle="123456789/281" submission-name="Publication"/> \
|
|
||||||
<name-map collection-handle="123456789/5" submission-name="Publication"/> \
|
|
||||||
<name-map collection-handle="123456789/8" submission-name="OrgUnit"/> \
|
|
||||||
<name-map collection-handle="123456789/6" submission-name="Person"/> \
|
|
||||||
<name-map collection-handle="123456789/279" submission-name="Person"/> \
|
|
||||||
<name-map collection-handle="123456789/7" submission-name="Project"/> \
|
|
||||||
<name-map collection-handle="123456789/280" submission-name="Project"/> \
|
|
||||||
<name-map collection-handle="123456789/28" submission-name="Journal"/> \
|
|
||||||
<name-map collection-handle="123456789/29" submission-name="JournalVolume"/> \
|
|
||||||
<name-map collection-handle="123456789/30" submission-name="JournalIssue"/>' /dspace/config/item-submission.xml
|
|
||||||
catalina.sh run
|
|
||||||
|
@@ -10,7 +10,6 @@
|
|||||||
# This is used by our GitHub CI at .github/workflows/build.yml
|
# This is used by our GitHub CI at .github/workflows/build.yml
|
||||||
# It is based heavily on the Backend's Docker Compose:
|
# It is based heavily on the Backend's Docker Compose:
|
||||||
# https://github.com/DSpace/DSpace/blob/main/docker-compose.yml
|
# https://github.com/DSpace/DSpace/blob/main/docker-compose.yml
|
||||||
version: '3.7'
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
services:
|
services:
|
||||||
@@ -33,11 +32,12 @@ services:
|
|||||||
# Tell Statistics to commit all views immediately instead of waiting on Solr's autocommit.
|
# Tell Statistics to commit all views immediately instead of waiting on Solr's autocommit.
|
||||||
# This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly.
|
# This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly.
|
||||||
solr__D__statistics__P__autoCommit: 'false'
|
solr__D__statistics__P__autoCommit: 'false'
|
||||||
|
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
||||||
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-8_x-test}"
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
image: dspace/dspace:dspace-7_x-test
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 8080
|
- published: 8080
|
||||||
target: 8080
|
target: 8080
|
||||||
@@ -45,46 +45,46 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
|
||||||
- solr_configs:/dspace/solr
|
|
||||||
# Ensure that the database is ready BEFORE starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
||||||
# 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any)
|
# 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any)
|
||||||
# 3. Finally, start Tomcat
|
# 3. Finally, start DSpace
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '-c'
|
- '-c'
|
||||||
- |
|
- |
|
||||||
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
|
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
|
||||||
/dspace/bin/dspace database migrate ignored
|
/dspace/bin/dspace database migrate ignored
|
||||||
catalina.sh run
|
java -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace
|
||||||
# DSpace database container
|
# DSpace database container
|
||||||
# NOTE: This is customized to use our loadsql image, so that we are using a database with existing test data
|
# NOTE: This is customized to use our loadsql image, so that we are using a database with existing test data
|
||||||
dspacedb:
|
dspacedb:
|
||||||
container_name: dspacedb
|
container_name: dspacedb
|
||||||
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-8_x}-loadsql"
|
||||||
environment:
|
environment:
|
||||||
# This LOADSQL should be kept in sync with the LOADSQL in
|
# This LOADSQL should be kept in sync with the LOADSQL in
|
||||||
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
|
||||||
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
||||||
|
# NOTE: currently there is no dspace8 version
|
||||||
LOADSQL: https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
|
LOADSQL: https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
|
||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
image: dspace/dspace-postgres-pgcrypto:loadsql
|
POSTGRES_PASSWORD: dspace
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
|
ports:
|
||||||
|
- published: 5432
|
||||||
|
target: 5432
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
|
# Keep Postgres data directory between reboots
|
||||||
- pgdata:/pgdata
|
- pgdata:/pgdata
|
||||||
# DSpace Solr container
|
# DSpace Solr container
|
||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
# Uses official Solr image at https://hub.docker.com/_/solr/
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-8_x}"
|
||||||
image: solr:8.11-slim
|
|
||||||
# Needs main 'dspace' container to start first to guarantee access to solr_configs
|
|
||||||
depends_on:
|
|
||||||
- dspace
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 8983
|
- published: 8983
|
||||||
target: 8983
|
target: 8983
|
||||||
@@ -92,9 +92,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
working_dir: /var/solr/data
|
working_dir: /var/solr/data
|
||||||
volumes:
|
volumes:
|
||||||
# Mount our "solr_configs" volume available under the Solr's configsets folder (in a 'dspace' subfolder)
|
|
||||||
# This copies the Solr configs from main 'dspace' container into 'dspacesolr' via that volume
|
|
||||||
- solr_configs:/opt/solr/server/solr/configsets/dspace
|
|
||||||
# Keep Solr data directory between reboots
|
# Keep Solr data directory between reboots
|
||||||
- solr_data:/var/solr/data
|
- solr_data:/var/solr/data
|
||||||
# Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr
|
# Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr
|
||||||
@@ -103,14 +100,20 @@ services:
|
|||||||
- '-c'
|
- '-c'
|
||||||
- |
|
- |
|
||||||
init-var-solr
|
init-var-solr
|
||||||
precreate-core authority /opt/solr/server/solr/configsets/dspace/authority
|
precreate-core authority /opt/solr/server/solr/configsets/authority
|
||||||
precreate-core oai /opt/solr/server/solr/configsets/dspace/oai
|
cp -r /opt/solr/server/solr/configsets/authority/* authority
|
||||||
precreate-core search /opt/solr/server/solr/configsets/dspace/search
|
precreate-core oai /opt/solr/server/solr/configsets/oai
|
||||||
precreate-core statistics /opt/solr/server/solr/configsets/dspace/statistics
|
cp -r /opt/solr/server/solr/configsets/oai/* oai
|
||||||
|
precreate-core search /opt/solr/server/solr/configsets/search
|
||||||
|
cp -r /opt/solr/server/solr/configsets/search/* search
|
||||||
|
precreate-core statistics /opt/solr/server/solr/configsets/statistics
|
||||||
|
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
|
||||||
|
precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
|
||||||
|
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
||||||
|
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
|
||||||
|
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
|
||||||
exec solr -f
|
exec solr -f
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
pgdata:
|
pgdata:
|
||||||
solr_data:
|
solr_data:
|
||||||
# Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above)
|
|
||||||
solr_configs:
|
|
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
# Docker Compose for running the DSpace Angular UI dist build
|
# Docker Compose for running the DSpace Angular UI dist build
|
||||||
# for previewing with the DSpace Demo site backend
|
# for previewing with the DSpace Demo site backend
|
||||||
version: '3.7'
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
services:
|
services:
|
||||||
@@ -24,10 +23,10 @@ services:
|
|||||||
# This is because Server Side Rendering (SSR) currently requires a public URL,
|
# This is because Server Side Rendering (SSR) currently requires a public URL,
|
||||||
# see this bug: https://github.com/DSpace/dspace-angular/issues/1485
|
# see this bug: https://github.com/DSpace/dspace-angular/issues/1485
|
||||||
DSPACE_REST_SSL: 'true'
|
DSPACE_REST_SSL: 'true'
|
||||||
DSPACE_REST_HOST: api7.dspace.org
|
DSPACE_REST_HOST: sandbox.dspace.org
|
||||||
DSPACE_REST_PORT: 443
|
DSPACE_REST_PORT: 443
|
||||||
DSPACE_REST_NAMESPACE: /server
|
DSPACE_REST_NAMESPACE: /server
|
||||||
image: dspace/dspace-angular:dspace-7_x-dist
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-8_x}-dist"
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: Dockerfile.dist
|
dockerfile: Dockerfile.dist
|
||||||
|
@@ -10,7 +10,6 @@
|
|||||||
# This is based heavily on the docker-compose.yml that is available in the DSpace/DSpace
|
# This is based heavily on the docker-compose.yml that is available in the DSpace/DSpace
|
||||||
# (Backend) at:
|
# (Backend) at:
|
||||||
# https://github.com/DSpace/DSpace/blob/main/docker-compose.yml
|
# https://github.com/DSpace/DSpace/blob/main/docker-compose.yml
|
||||||
version: '3.7'
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
ipam:
|
ipam:
|
||||||
@@ -29,8 +28,9 @@ services:
|
|||||||
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||||
# dspace.dir, dspace.server.url, dspace.ui.url and dspace.name
|
# dspace.dir, dspace.server.url, dspace.ui.url and dspace.name
|
||||||
dspace__P__dir: /dspace
|
dspace__P__dir: /dspace
|
||||||
dspace__P__server__P__url: http://localhost:8080/server
|
# Uncomment to set a non-default value for dspace.server.url or dspace.ui.url
|
||||||
dspace__P__ui__P__url: http://localhost:4000
|
# dspace__P__server__P__url: http://localhost:8080/server
|
||||||
|
# dspace__P__ui__P__url: http://localhost:4000
|
||||||
dspace__P__name: 'DSpace Started with Docker Compose'
|
dspace__P__name: 'DSpace Started with Docker Compose'
|
||||||
# db.url: Ensure we are using the 'dspacedb' image for our database
|
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||||
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||||
@@ -39,55 +39,55 @@ services:
|
|||||||
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
|
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
|
||||||
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
||||||
proxies__P__trusted__P__ipranges: '172.23.0'
|
proxies__P__trusted__P__ipranges: '172.23.0'
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}"
|
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
||||||
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-8_x-test}"
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 8080
|
- published: 8080
|
||||||
target: 8080
|
target: 8080
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
|
# Keep DSpace assetstore directory between reboots
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
|
||||||
- solr_configs:/dspace/solr
|
|
||||||
# Ensure that the database is ready BEFORE starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
||||||
# 2. Then, run database migration to init database tables
|
# 2. Then, run database migration to init database tables
|
||||||
# 3. Finally, start Tomcat
|
# 3. Finally, start DSpace
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '-c'
|
- '-c'
|
||||||
- |
|
- |
|
||||||
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
|
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
|
||||||
/dspace/bin/dspace database migrate
|
/dspace/bin/dspace database migrate
|
||||||
catalina.sh run
|
java -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace
|
||||||
# DSpace database container
|
# DSpace database container
|
||||||
dspacedb:
|
dspacedb:
|
||||||
container_name: dspacedb
|
container_name: dspacedb
|
||||||
|
# Uses a custom Postgres image with pgcrypto installed
|
||||||
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-8_x}"
|
||||||
environment:
|
environment:
|
||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
image: dspace/dspace-postgres-pgcrypto
|
POSTGRES_PASSWORD: dspace
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 5432
|
- published: 5432
|
||||||
target: 5432
|
target: 5432
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
|
# Keep Postgres data directory between reboots
|
||||||
- pgdata:/pgdata
|
- pgdata:/pgdata
|
||||||
# DSpace Solr container
|
# DSpace Solr container
|
||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-8_x}"
|
||||||
# Needs main 'dspace' container to start first to guarantee access to solr_configs
|
|
||||||
depends_on:
|
|
||||||
- dspace
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 8983
|
- published: 8983
|
||||||
target: 8983
|
target: 8983
|
||||||
@@ -101,7 +101,7 @@ services:
|
|||||||
# * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op
|
# * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op
|
||||||
# * Second, copy configsets to this core:
|
# * Second, copy configsets to this core:
|
||||||
# Updates to Solr configs require the container to be rebuilt/restarted:
|
# Updates to Solr configs require the container to be rebuilt/restarted:
|
||||||
# `docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr`
|
# `docker compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr`
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '-c'
|
- '-c'
|
||||||
@@ -115,10 +115,12 @@ services:
|
|||||||
cp -r /opt/solr/server/solr/configsets/search/* search
|
cp -r /opt/solr/server/solr/configsets/search/* search
|
||||||
precreate-core statistics /opt/solr/server/solr/configsets/statistics
|
precreate-core statistics /opt/solr/server/solr/configsets/statistics
|
||||||
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
|
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
|
||||||
|
precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
|
||||||
|
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
||||||
|
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
|
||||||
|
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
|
||||||
exec solr -f
|
exec solr -f
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
pgdata:
|
pgdata:
|
||||||
solr_data:
|
solr_data:
|
||||||
# Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above)
|
|
||||||
solr_configs:
|
|
||||||
|
@@ -9,7 +9,6 @@
|
|||||||
# Docker Compose for running the DSpace Angular UI for testing/development
|
# Docker Compose for running the DSpace Angular UI for testing/development
|
||||||
# Requires also running a REST API backend (either locally or remotely),
|
# Requires also running a REST API backend (either locally or remotely),
|
||||||
# for example via 'docker-compose-rest.yml'
|
# for example via 'docker-compose-rest.yml'
|
||||||
version: '3.7'
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
services:
|
services:
|
||||||
@@ -24,7 +23,7 @@ services:
|
|||||||
DSPACE_REST_HOST: localhost
|
DSPACE_REST_HOST: localhost
|
||||||
DSPACE_REST_PORT: 8080
|
DSPACE_REST_PORT: 8080
|
||||||
DSPACE_REST_NAMESPACE: /server
|
DSPACE_REST_NAMESPACE: /server
|
||||||
image: dspace/dspace-angular:dspace-7_x
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-8_x}"
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
@@ -48,7 +48,7 @@ dspace-angular connects to your DSpace installation by using its REST endpoint.
|
|||||||
```yaml
|
```yaml
|
||||||
rest:
|
rest:
|
||||||
ssl: true
|
ssl: true
|
||||||
host: api7.dspace.org
|
host: demo.dspace.org
|
||||||
port: 443
|
port: 443
|
||||||
nameSpace: /server
|
nameSpace: /server
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ rest:
|
|||||||
Alternately you can set the following environment variables. If any of these are set, it will override all configuration files:
|
Alternately you can set the following environment variables. If any of these are set, it will override all configuration files:
|
||||||
```
|
```
|
||||||
DSPACE_REST_SSL=true
|
DSPACE_REST_SSL=true
|
||||||
DSPACE_REST_HOST=api7.dspace.org
|
DSPACE_REST_HOST=demo.dspace.org
|
||||||
DSPACE_REST_PORT=443
|
DSPACE_REST_PORT=443
|
||||||
DSPACE_REST_NAMESPACE=/server
|
DSPACE_REST_NAMESPACE=/server
|
||||||
```
|
```
|
||||||
|
5
docs/lint/html/index.md
Normal file
5
docs/lint/html/index.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[DSpace ESLint plugins](../../../lint/README.md) > HTML rules
|
||||||
|
_______
|
||||||
|
|
||||||
|
- [`dspace-angular-html/themed-component-usages`](./rules/themed-component-usages.md): Themeable components should be used via the selector of their `ThemedComponent` wrapper class
|
||||||
|
- [`dspace-angular-html/no-disabled-attribute-on-button`](./rules/no-disabled-attribute-on-button.md): Buttons should use the `dsBtnDisabled` directive instead of the HTML `disabled` attribute.
|
78
docs/lint/html/rules/no-disabled-attribute-on-button.md
Normal file
78
docs/lint/html/rules/no-disabled-attribute-on-button.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [HTML rules](../index.md) > `dspace-angular-html/no-disabled-attribute-on-button`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Buttons should use the `dsBtnDisabled` directive instead of the HTML `disabled` attribute.
|
||||||
|
This should be done to ensure that users with a screen reader are able to understand that the a button button is present, and that it is disabled.
|
||||||
|
The native html disabled attribute does not allow users to navigate to the button by keyboard, and thus they have no way of knowing that the button is present.
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/html/no-disabled-attribute-on-button.ts)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### should use [dsBtnDisabled] in HTML templates
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button [dsBtnDisabled]="true">Submit</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
##### disabled attribute is still valid on non-button elements
|
||||||
|
|
||||||
|
```html
|
||||||
|
<input disabled>
|
||||||
|
```
|
||||||
|
|
||||||
|
##### [disabled] attribute is still valid on non-button elements
|
||||||
|
|
||||||
|
```html
|
||||||
|
<input [disabled]="true">
|
||||||
|
```
|
||||||
|
|
||||||
|
##### angular dynamic attributes that use disabled are still valid
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button [class.disabled]="isDisabled">Submit</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code & automatic fixes
|
||||||
|
|
||||||
|
##### should not use disabled attribute in HTML templates
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button disabled>Submit</button>
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute.
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```html
|
||||||
|
<button [dsBtnDisabled]="true">Submit</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### should not use [disabled] attribute in HTML templates
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button [disabled]="true">Submit</button>
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute.
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```html
|
||||||
|
<button [dsBtnDisabled]="true">Submit</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
110
docs/lint/html/rules/themed-component-usages.md
Normal file
110
docs/lint/html/rules/themed-component-usages.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [HTML rules](../index.md) > `dspace-angular-html/themed-component-usages`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Themeable components should be used via the selector of their `ThemedComponent` wrapper class
|
||||||
|
|
||||||
|
This ensures that custom themes can correctly override _all_ instances of this component.
|
||||||
|
The only exception to this rule are unit tests, where we may want to use the base component in order to keep the test setup simple.
|
||||||
|
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/html/themed-component-usages.ts)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### use no-prefix selectors in HTML templates
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ds-test-themeable/>
|
||||||
|
<ds-test-themeable></ds-test-themeable>
|
||||||
|
<ds-test-themeable [test]="something"></ds-test-themeable>
|
||||||
|
```
|
||||||
|
|
||||||
|
##### use no-prefix selectors in TypeScript templates
|
||||||
|
|
||||||
|
```html
|
||||||
|
@Component({
|
||||||
|
template: '<ds-test-themeable></ds-test-themeable>'
|
||||||
|
})
|
||||||
|
class Test {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### use no-prefix selectors in TypeScript test templates
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/test.spec.ts`
|
||||||
|
|
||||||
|
```html
|
||||||
|
@Component({
|
||||||
|
template: '<ds-test-themeable></ds-test-themeable>'
|
||||||
|
})
|
||||||
|
class Test {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### base selectors are also allowed in TypeScript test templates
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/test.spec.ts`
|
||||||
|
|
||||||
|
```html
|
||||||
|
@Component({
|
||||||
|
template: '<ds-base-test-themeable></ds-base-test-themeable>'
|
||||||
|
})
|
||||||
|
class Test {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code & automatic fixes
|
||||||
|
|
||||||
|
##### themed override selectors are not allowed in HTML templates
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ds-themed-test-themeable/>
|
||||||
|
<ds-themed-test-themeable></ds-themed-test-themeable>
|
||||||
|
<ds-themed-test-themeable [test]="something"></ds-themed-test-themeable>
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper's selector
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper's selector
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper's selector
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```html
|
||||||
|
<ds-test-themeable/>
|
||||||
|
<ds-test-themeable></ds-test-themeable>
|
||||||
|
<ds-test-themeable [test]="something"></ds-test-themeable>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### base selectors are not allowed in HTML templates
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ds-base-test-themeable/>
|
||||||
|
<ds-base-test-themeable></ds-base-test-themeable>
|
||||||
|
<ds-base-test-themeable [test]="something"></ds-base-test-themeable>
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper's selector
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper's selector
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper's selector
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```html
|
||||||
|
<ds-test-themeable/>
|
||||||
|
<ds-test-themeable></ds-test-themeable>
|
||||||
|
<ds-test-themeable [test]="something"></ds-test-themeable>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
6
docs/lint/ts/index.md
Normal file
6
docs/lint/ts/index.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[DSpace ESLint plugins](../../../lint/README.md) > TypeScript rules
|
||||||
|
_______
|
||||||
|
|
||||||
|
- [`dspace-angular-ts/themed-component-classes`](./rules/themed-component-classes.md): Formatting rules for themeable component classes
|
||||||
|
- [`dspace-angular-ts/themed-component-selectors`](./rules/themed-component-selectors.md): Themeable component selectors should follow the DSpace convention
|
||||||
|
- [`dspace-angular-ts/themed-component-usages`](./rules/themed-component-usages.md): Themeable components should be used via their `ThemedComponent` wrapper class
|
257
docs/lint/ts/rules/themed-component-classes.md
Normal file
257
docs/lint/ts/rules/themed-component-classes.md
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-component-classes`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Formatting rules for themeable component classes
|
||||||
|
|
||||||
|
- All themeable components must be standalone.
|
||||||
|
- The base component must always be imported in the `ThemedComponent` wrapper. This ensures that it is always sufficient to import just the wrapper whenever we use the component.
|
||||||
|
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/ts/themed-component-classes.ts)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### Regular non-themeable component
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-something',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
class Something {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Base component
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-base-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
class TestThemeableTomponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Wrapper component
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
TestThemeableComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableTomponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Override component
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
class Override extends BaseComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code & automatic fixes
|
||||||
|
|
||||||
|
##### Base component must be standalone
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-base-test-themable',
|
||||||
|
})
|
||||||
|
class TestThemeableComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components must be standalone
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-base-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
class TestThemeableComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Wrapper component must be standalone and import base component
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable component wrapper classes must be standalone and import the base class
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
imports: [TestThemeableComponent],
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Wrapper component must import base component (array present but empty)
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
imports: [],
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themed component wrapper classes must only import the base class
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
imports: [TestThemeableComponent],
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Wrapper component must import base component (array is wrong)
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SomethingElse } from './somewhere-else';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
SomethingElse,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themed component wrapper classes must only import the base class
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { SomethingElse } from './somewhere-else';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
imports: [TestThemeableComponent],
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Wrapper component must import base component (array is wrong)
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Something, SomethingElse } from './somewhere-else';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
SomethingElse,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themed component wrapper classes must only import the base class
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { Something, SomethingElse } from './somewhere-else';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
imports: [TestThemeableComponent],
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Override component must be standalone
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-test-themable',
|
||||||
|
})
|
||||||
|
class Override extends BaseComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components must be standalone
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-test-themable',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
class Override extends BaseComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
156
docs/lint/ts/rules/themed-component-selectors.md
Normal file
156
docs/lint/ts/rules/themed-component-selectors.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-component-selectors`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Themeable component selectors should follow the DSpace convention
|
||||||
|
|
||||||
|
Each themeable component is comprised of a base component, a wrapper component and any number of themed components
|
||||||
|
- Base components should have a selector starting with `ds-base-`
|
||||||
|
- Themed components should have a selector starting with `ds-themed-`
|
||||||
|
- Wrapper components should have a selector starting with `ds-`, but not `ds-base-` or `ds-themed-`
|
||||||
|
- This is the regular DSpace selector prefix
|
||||||
|
- **When making a regular component themeable, its selector prefix should be changed to `ds-base-`, and the new wrapper's component should reuse the previous selector**
|
||||||
|
|
||||||
|
Unit tests are exempt from this rule, because they may redefine components using the same class name as other themeable components elsewhere in the source.
|
||||||
|
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/ts/themed-component-selectors.ts)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### Regular non-themeable component selector
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-something',
|
||||||
|
})
|
||||||
|
class Something {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Themeable component selector should replace the original version, unthemed version should be changed to ds-base-
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-base-something',
|
||||||
|
})
|
||||||
|
class Something {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-something',
|
||||||
|
})
|
||||||
|
class ThemedSomething extends ThemedComponent<Something> {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-something',
|
||||||
|
})
|
||||||
|
class OverrideSomething extends Something {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Other themed component wrappers should not interfere
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-something',
|
||||||
|
})
|
||||||
|
class Something {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-something-else',
|
||||||
|
})
|
||||||
|
class ThemedSomethingElse extends ThemedComponent<SomethingElse> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code & automatic fixes
|
||||||
|
|
||||||
|
##### Wrong selector for base component
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-something',
|
||||||
|
})
|
||||||
|
class TestThemeableComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Unthemed version of themeable component should have a selector starting with 'ds-base-'
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-base-something',
|
||||||
|
})
|
||||||
|
class TestThemeableComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Wrong selector for wrapper component
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-something',
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themed component wrapper of themeable component shouldn't have a selector starting with 'ds-themed-'
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-something',
|
||||||
|
})
|
||||||
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Wrong selector for theme override
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-something',
|
||||||
|
})
|
||||||
|
class TestThememeableComponent extends BaseComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Theme override of themeable component should have a selector starting with 'ds-themed-'
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-something',
|
||||||
|
})
|
||||||
|
class TestThememeableComponent extends BaseComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
332
docs/lint/ts/rules/themed-component-usages.md
Normal file
332
docs/lint/ts/rules/themed-component-usages.md
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-component-usages`
|
||||||
|
_______
|
||||||
|
|
||||||
|
Themeable components should be used via their `ThemedComponent` wrapper class
|
||||||
|
|
||||||
|
This ensures that custom themes can correctly override _all_ instances of this component.
|
||||||
|
There are a few exceptions where the base class can still be used:
|
||||||
|
- Class declaration expressions (otherwise we can't declare, extend or override the class in the first place)
|
||||||
|
- Angular modules (except for routing modules)
|
||||||
|
- Angular `@ViewChild` decorators
|
||||||
|
- Type annotations
|
||||||
|
|
||||||
|
|
||||||
|
_______
|
||||||
|
|
||||||
|
[Source code](../../../../lint/src/rules/ts/themed-component-usages.ts)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
#### Valid code
|
||||||
|
|
||||||
|
##### allow wrapper class usages
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ThemedTestThemeableComponent } from './app/test/themed-test-themeable.component';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
a: ThemedTestThemeableComponent,
|
||||||
|
b: ChipsComponent,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### allow base class in class declaration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class TestThemeableComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### allow inheriting from base class
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TestThemeableComponent } from './app/test/test-themeable.component';
|
||||||
|
|
||||||
|
export class ThemedAdminSidebarComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### allow base class in ViewChild
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TestThemeableComponent } from './app/test/test-themeable.component';
|
||||||
|
|
||||||
|
export class Something {
|
||||||
|
@ViewChild(TestThemeableComponent) test: TestThemeableComponent;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### allow wrapper selectors in test queries
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
By.css('ds-themeable');
|
||||||
|
By.css('#test > ds-themeable > #nest');
|
||||||
|
```
|
||||||
|
|
||||||
|
##### allow wrapper selectors in cypress queries
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
By.css('ds-themeable');
|
||||||
|
By.css('#test > ds-themeable > #nest');
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Invalid code & automatic fixes
|
||||||
|
|
||||||
|
##### disallow direct usages of base class
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TestThemeableComponent } from './app/test/test-themeable.component';
|
||||||
|
import { TestComponent } from './app/test/test.component';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
a: TestThemeableComponent,
|
||||||
|
b: TestComponent,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { ThemedTestThemeableComponent } from './app/test/themed-test-themeable.component';
|
||||||
|
import { TestComponent } from './app/test/test.component';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
a: ThemedTestThemeableComponent,
|
||||||
|
b: TestComponent,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### disallow direct usages of base class, keep other imports
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Something, TestThemeableComponent } from './app/test/test-themeable.component';
|
||||||
|
import { TestComponent } from './app/test/test.component';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
a: TestThemeableComponent,
|
||||||
|
b: TestComponent,
|
||||||
|
c: Something,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { Something } from './app/test/test-themeable.component';
|
||||||
|
import { ThemedTestThemeableComponent } from './app/test/themed-test-themeable.component';
|
||||||
|
import { TestComponent } from './app/test/test.component';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
a: ThemedTestThemeableComponent,
|
||||||
|
b: TestComponent,
|
||||||
|
c: Something,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### handle array replacements correctly
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const DECLARATIONS = [
|
||||||
|
Something,
|
||||||
|
TestThemeableComponent,
|
||||||
|
Something,
|
||||||
|
ThemedTestThemeableComponent,
|
||||||
|
];
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
const DECLARATIONS = [
|
||||||
|
Something,
|
||||||
|
Something,
|
||||||
|
ThemedTestThemeableComponent,
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### disallow override selector in test queries
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
By.css('ds-themed-themeable');
|
||||||
|
By.css('#test > ds-themed-themeable > #nest');
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
By.css('ds-themeable');
|
||||||
|
By.css('#test > ds-themeable > #nest');
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### disallow base selector in test queries
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
By.css('ds-base-themeable');
|
||||||
|
By.css('#test > ds-base-themeable > #nest');
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
By.css('ds-themeable');
|
||||||
|
By.css('#test > ds-themeable > #nest');
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### disallow override selector in cypress queries
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
cy.get('ds-themed-themeable');
|
||||||
|
cy.get('#test > ds-themed-themeable > #nest');
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
cy.get('ds-themeable');
|
||||||
|
cy.get('#test > ds-themeable > #nest');
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### disallow base selector in cypress queries
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
cy.get('ds-base-themeable');
|
||||||
|
cy.get('#test > ds-base-themeable > #nest');
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
cy.get('ds-themeable');
|
||||||
|
cy.get('#test > ds-themeable > #nest');
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### edge case: unable to find usage node through usage token, but import is still flagged and fixed
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test/app/test/other-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { Context } from './app/core/shared/context.model';
|
||||||
|
import { TestThemeableComponent } from '../../../../app/test/test-themeable.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [TestThemeableComponent],
|
||||||
|
})
|
||||||
|
export class UsageComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { Context } from './app/core/shared/context.model';
|
||||||
|
import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-themeable.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [ThemedTestThemeableComponent],
|
||||||
|
})
|
||||||
|
export class UsageComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### edge case edge case: both are imported, only wrapper is retained
|
||||||
|
|
||||||
|
Filename: `lint/test/fixture/src/themes/test/app/test/other-themeable.component.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { Context } from './app/core/shared/context.model';
|
||||||
|
import { TestThemeableComponent } from '../../../../app/test/test-themeable.component';
|
||||||
|
import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-themeable.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [TestThemeableComponent, ThemedTestThemeableComponent],
|
||||||
|
})
|
||||||
|
export class UsageComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Will produce the following error(s):
|
||||||
|
```
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
Themeable components should be used via their ThemedComponent wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of `yarn lint --fix`:
|
||||||
|
```typescript
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { Context } from './app/core/shared/context.model';
|
||||||
|
import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-themeable.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [ThemedTestThemeableComponent],
|
||||||
|
})
|
||||||
|
export class UsageComponent {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -15,7 +15,10 @@ module.exports = function (config) {
|
|||||||
],
|
],
|
||||||
client: {
|
client: {
|
||||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||||
captureConsole: false
|
captureConsole: false,
|
||||||
|
jasmine: {
|
||||||
|
failSpecWithNoExpectations: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
dir: require('path').join(__dirname, './coverage/dspace-angular'),
|
dir: require('path').join(__dirname, './coverage/dspace-angular'),
|
||||||
|
3
lint/.gitignore
vendored
Normal file
3
lint/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/dist/
|
||||||
|
/coverage/
|
||||||
|
/node-modules/
|
50
lint/README.md
Normal file
50
lint/README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# DSpace ESLint plugins
|
||||||
|
|
||||||
|
Custom ESLint rules for DSpace Angular peculiarities.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
These plugins are included with the rest of our ESLint configuration in [.eslintc.json](../.eslintrc.json). Individual rules can be configured or disabled there, like usual.
|
||||||
|
- In order for the new rules to be picked up by your IDE, you should first run `yarn build:lint` to build the plugins.
|
||||||
|
- This will also happen automatically each time `yarn lint` is run.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The rules are split up into plugins by language:
|
||||||
|
- [TypeScript rules](../docs/lint/ts/index.md)
|
||||||
|
- [HTML rules](../docs/lint/html/index.md)
|
||||||
|
|
||||||
|
> Run `yarn docs:lint` to generate this documentation!
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- All rules are written in TypeScript and compiled into [`dist`](./dist)
|
||||||
|
- The plugins are linked into the main project dependencies from here
|
||||||
|
- These directories already contain the necessary `package.json` files to mark them as ESLint plugins
|
||||||
|
- Rule source files are structured, so they can be imported all in one go
|
||||||
|
- Each rule must export the following:
|
||||||
|
- `Messages`: an Enum of error message IDs
|
||||||
|
- `info`: metadata about this rule (name, description, messages, options, ...)
|
||||||
|
- `rule`: the implementation of the rule
|
||||||
|
- `tests`: the tests for this rule, as a set of valid/invalid code snippets. These snippets are used as example in the documentation.
|
||||||
|
- New rules should be added to their plugin's `index.ts`
|
||||||
|
- Some useful links
|
||||||
|
- [Developing ESLint plugins](https://eslint.org/docs/latest/extend/plugins)
|
||||||
|
- [Custom rules in typescript-eslint](https://typescript-eslint.io/developers/custom-rules)
|
||||||
|
- [Angular ESLint](https://github.com/angular-eslint/angular-eslint)
|
||||||
|
|
||||||
|
### Parsing project metadata in advance ~ TypeScript AST
|
||||||
|
|
||||||
|
While it is possible to retain persistent state between files during the linting process, it becomes quite complicated if the content of one file determines how we want to lint another file.
|
||||||
|
Because the two files may be linted out of order, we may not know whether the first file is wrong before we pass by the second. This means that we cannot report or fix the issue, because the first file is already detached from the linting context.
|
||||||
|
|
||||||
|
For example, we cannot consistently determine which components are themeable (i.e. have a `ThemedComponent` wrapper) while linting.
|
||||||
|
To work around this issue, we construct a registry of themeable components _before_ linting anything.
|
||||||
|
- We don't have a good way to hook into the ESLint parser at this time
|
||||||
|
- Instead, we leverage the actual TypeScript AST parser
|
||||||
|
- Retrieve all `ThemedComponent` wrapper files by the pattern of their path (`themed-*.component.ts`)
|
||||||
|
- Determine the themed component they're linked to (by the actual type annotation/import path, since filenames are prone to errors)
|
||||||
|
- Store metadata describing these component pairs in a global registry that can be shared between rules
|
||||||
|
- This only needs to happen once, and only takes a fraction of a second (for ~100 themeable components)
|
6
lint/dist/src/rules/html/package.json
vendored
Normal file
6
lint/dist/src/rules/html/package.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "eslint-plugin-dspace-angular-html",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "./index.js",
|
||||||
|
"private": true
|
||||||
|
}
|
6
lint/dist/src/rules/ts/package.json
vendored
Normal file
6
lint/dist/src/rules/ts/package.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "eslint-plugin-dspace-angular-ts",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "./index.js",
|
||||||
|
"private": true
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user