mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
Merge branch 'main' into w2p-108608_created-search-scope-selector_contribute-main
This commit is contained in:
@@ -25,4 +25,6 @@ npm-debug.log.*
|
|||||||
|
|
||||||
# Webpack files
|
# Webpack files
|
||||||
webpack.records.json
|
webpack.records.json
|
||||||
package-lock.json
|
|
||||||
|
# Yarn no longer used
|
||||||
|
yarn.lock
|
||||||
|
@@ -11,7 +11,13 @@
|
|||||||
"eslint-plugin-jsonc",
|
"eslint-plugin-jsonc",
|
||||||
"eslint-plugin-rxjs",
|
"eslint-plugin-rxjs",
|
||||||
"eslint-plugin-simple-import-sort",
|
"eslint-plugin-simple-import-sort",
|
||||||
"eslint-plugin-import-newlines"
|
"eslint-plugin-import-newlines",
|
||||||
|
"eslint-plugin-jsonc",
|
||||||
|
"dspace-angular-ts",
|
||||||
|
"dspace-angular-html"
|
||||||
|
],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"lint/test/fixture"
|
||||||
],
|
],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
@@ -21,7 +27,8 @@
|
|||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"project": [
|
"project": [
|
||||||
"./tsconfig.json",
|
"./tsconfig.json",
|
||||||
"./cypress/tsconfig.json"
|
"./cypress/tsconfig.json",
|
||||||
|
"./lint/tsconfig.json"
|
||||||
],
|
],
|
||||||
"createDefaultProgram": true
|
"createDefaultProgram": true
|
||||||
},
|
},
|
||||||
@@ -38,7 +45,10 @@
|
|||||||
"error",
|
"error",
|
||||||
2,
|
2,
|
||||||
{
|
{
|
||||||
"SwitchCase": 1
|
"SwitchCase": 1,
|
||||||
|
"ignoredNodes": [
|
||||||
|
"ClassBody.body > PropertyDefinition[decorators.length > 0] > .key"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"max-classes-per-file": [
|
"max-classes-per-file": [
|
||||||
@@ -152,10 +162,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",
|
||||||
@@ -213,6 +223,15 @@
|
|||||||
"@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",
|
||||||
|
|
||||||
@@ -239,7 +258,12 @@
|
|||||||
"method"
|
"method"
|
||||||
],
|
],
|
||||||
|
|
||||||
"rxjs/no-nested-subscribe": "off" // todo: go over _all_ cases
|
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -254,7 +278,10 @@
|
|||||||
"createDefaultProgram": true
|
"createDefaultProgram": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"prefer-const": "off"
|
"prefer-const": "off",
|
||||||
|
|
||||||
|
// Custom DSpace Angular rules
|
||||||
|
"dspace-angular-ts/themed-component-usages": "error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -263,7 +290,11 @@
|
|||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:@angular-eslint/template/recommended"
|
"plugin:@angular-eslint/template/recommended"
|
||||||
]
|
],
|
||||||
|
"rules": {
|
||||||
|
// Custom DSpace Angular rules
|
||||||
|
"dspace-angular-html/themed-component-usages": "error"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
|
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
|
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,16 +7,16 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
## Describe the bug
|
||||||
A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem & what *web browser* you were using. Link to examples if they are public.
|
A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem & what *web browser* you were using. Link to examples if they are public.
|
||||||
|
|
||||||
**To Reproduce**
|
## To Reproduce
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
1. Do this
|
1. Do this
|
||||||
2. Then this...
|
2. Then this...
|
||||||
|
|
||||||
**Expected behavior**
|
## Expected behavior
|
||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
**Related work**
|
## Related work
|
||||||
Link to any related tickets or PRs here.
|
Link to any related tickets or PRs here.
|
||||||
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,14 +7,14 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
## Is your feature request related to a problem? Please describe.
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
A clear and concise description of what the problem or use case is. For example, I'm always frustrated when [...]
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
## Describe the solution you'd like
|
||||||
A clear and concise description of what you want to happen.
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
**Describe alternatives or workarounds you've considered**
|
## Describe alternatives or workarounds you've considered
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
**Additional context**
|
## Additional information
|
||||||
Add any other context or screenshots about the feature request here.
|
Add any other information, related tickets or screenshots about the feature request here.
|
||||||
|
298
.github/dependabot.yml
vendored
Normal file
298
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
#-------------------
|
||||||
|
# DSpace's dependabot rules. Enables npm updates for all dependencies on a weekly basis
|
||||||
|
# for main and any maintenance branches. Security updates only apply to main.
|
||||||
|
#-------------------
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
###############
|
||||||
|
## Main branch
|
||||||
|
###############
|
||||||
|
# NOTE: At this time, "security-updates" rules only apply if "target-branch" is unspecified
|
||||||
|
# So, only this first section can include "applies-to: security-updates"
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
# Allow up to 10 open PRs for dependencies
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
# Group together Angular package upgrades
|
||||||
|
groups:
|
||||||
|
# Group together all minor/patch version updates for Angular in a single PR
|
||||||
|
angular:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@angular*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all security updates for Angular. Only accept minor/patch types.
|
||||||
|
angular-security:
|
||||||
|
applies-to: security-updates
|
||||||
|
patterns:
|
||||||
|
- "@angular*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all minor/patch version updates for NgRx in a single PR
|
||||||
|
ngrx:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@ngrx*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all security updates for NgRx. Only accept minor/patch types.
|
||||||
|
ngrx-security:
|
||||||
|
applies-to: security-updates
|
||||||
|
patterns:
|
||||||
|
- "@ngrx*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all patch version updates for eslint in a single PR
|
||||||
|
eslint:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@typescript-eslint*"
|
||||||
|
- "eslint*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all security updates for eslint.
|
||||||
|
eslint-security:
|
||||||
|
applies-to: security-updates
|
||||||
|
patterns:
|
||||||
|
- "@typescript-eslint*"
|
||||||
|
- "eslint*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any testing related version updates
|
||||||
|
testing:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@cypress*"
|
||||||
|
- "axe-*"
|
||||||
|
- "cypress*"
|
||||||
|
- "jasmine*"
|
||||||
|
- "karma*"
|
||||||
|
- "ng-mocks"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any testing related security updates
|
||||||
|
testing-security:
|
||||||
|
applies-to: security-updates
|
||||||
|
patterns:
|
||||||
|
- "@cypress*"
|
||||||
|
- "axe-*"
|
||||||
|
- "cypress*"
|
||||||
|
- "jasmine*"
|
||||||
|
- "karma*"
|
||||||
|
- "ng-mocks"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any postcss related version updates
|
||||||
|
postcss:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "postcss*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any postcss related security updates
|
||||||
|
postcss-security:
|
||||||
|
applies-to: security-updates
|
||||||
|
patterns:
|
||||||
|
- "postcss*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any sass related version updates
|
||||||
|
sass:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "sass*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any sass related security updates
|
||||||
|
sass-security:
|
||||||
|
applies-to: security-updates
|
||||||
|
patterns:
|
||||||
|
- "sass*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any webpack related version updates
|
||||||
|
webpack:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "webpack*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any webpack related seurity updates
|
||||||
|
webpack-security:
|
||||||
|
applies-to: security-updates
|
||||||
|
patterns:
|
||||||
|
- "webpack*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
ignore:
|
||||||
|
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
|
#####################
|
||||||
|
## dspace-8_x branch
|
||||||
|
#####################
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
target-branch: dspace-8_x
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
# Allow up to 10 open PRs for dependencies
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
# Group together Angular package upgrades
|
||||||
|
groups:
|
||||||
|
# Group together all patch version updates for Angular in a single PR
|
||||||
|
angular:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@angular*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all minor/patch version updates for NgRx in a single PR
|
||||||
|
ngrx:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@ngrx*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all patch version updates for eslint in a single PR
|
||||||
|
eslint:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@typescript-eslint*"
|
||||||
|
- "eslint*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any testing related version updates
|
||||||
|
testing:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@cypress*"
|
||||||
|
- "axe-*"
|
||||||
|
- "cypress*"
|
||||||
|
- "jasmine*"
|
||||||
|
- "karma*"
|
||||||
|
- "ng-mocks"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any postcss related version updates
|
||||||
|
postcss:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "postcss*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any sass related version updates
|
||||||
|
sass:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "sass*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any webpack related version updates
|
||||||
|
webpack:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "webpack*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
ignore:
|
||||||
|
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
|
#####################
|
||||||
|
## dspace-7_x branch
|
||||||
|
#####################
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
target-branch: dspace-7_x
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
# Allow up to 10 open PRs for dependencies
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
# Group together Angular package upgrades
|
||||||
|
groups:
|
||||||
|
# Group together all minor/patch version updates for Angular in a single PR
|
||||||
|
angular:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@angular*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all minor/patch version updates for NgRx in a single PR
|
||||||
|
ngrx:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@ngrx*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together all patch version updates for eslint in a single PR
|
||||||
|
eslint:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@typescript-eslint*"
|
||||||
|
- "eslint*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any testing related version updates
|
||||||
|
testing:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "@cypress*"
|
||||||
|
- "axe-*"
|
||||||
|
- "cypress*"
|
||||||
|
- "jasmine*"
|
||||||
|
- "karma*"
|
||||||
|
- "ng-mocks"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any postcss related version updates
|
||||||
|
postcss:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "postcss*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
# Group together any sass related version updates
|
||||||
|
sass:
|
||||||
|
applies-to: version-updates
|
||||||
|
patterns:
|
||||||
|
- "sass*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
ignore:
|
||||||
|
# 7.x Cannot update Webpack past v5.76.1 as later versions not supported by Angular 15
|
||||||
|
# See also https://github.com/DSpace/dspace-angular/pull/3283#issuecomment-2372488489
|
||||||
|
- dependency-name: "webpack"
|
||||||
|
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major"]
|
21
.github/pull_request_template.md
vendored
21
.github/pull_request_template.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
## References
|
## References
|
||||||
_Add references/links to any related issues or PRs. These may include:_
|
_Add references/links to any related issues or PRs. These may include:_
|
||||||
* Fixes #`issue-number` (if this fixes an issue ticket)
|
* Fixes #issue-number (if this fixes an issue ticket)
|
||||||
* Requires DSpace/DSpace#`pr-number` (if a REST API PR is required to test this)
|
* Requires DSpace/DSpace#pr-number (if a REST API PR is required to test this)
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
Short summary of changes (1-2 sentences).
|
Short summary of changes (1-2 sentences).
|
||||||
@@ -16,13 +16,18 @@ List of changes in this PR:
|
|||||||
**Include guidance for how to test or review your PR.** This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes.
|
**Include guidance for how to test or review your PR.** This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes.
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_
|
_This checklist provides a reminder of what we are going to look for when reviewing your PR. You do not need to complete this checklist prior creating your PR (draft PRs are always welcome).
|
||||||
|
However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_
|
||||||
|
|
||||||
- [ ] My PR is small in size (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible.
|
- [ ] My PR is **created against the `main` branch** of code (unless it is a backport or is fixing an issue specific to an older branch).
|
||||||
- [ ] My PR passes [ESLint](https://eslint.org/) validation using `yarn lint`
|
- [ ] My PR is **small in size** (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible.
|
||||||
- [ ] My PR doesn't introduce circular dependencies (verified via `yarn check-circ-deps`)
|
- [ ] My PR **passes [ESLint](https://eslint.org/)** validation using `npm run lint`
|
||||||
- [ ] My PR includes [TypeDoc](https://typedoc.org/) comments for _all new (or modified) public methods and classes_. It also includes TypeDoc for large or complex private methods.
|
- [ ] My PR **doesn't introduce circular dependencies** (verified via `npm run check-circ-deps`)
|
||||||
- [ ] My PR passes all specs/tests and includes new/updated specs or tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
- [ ] My PR **includes [TypeDoc](https://typedoc.org/) comments** for _all new (or modified) public methods and classes_. It also includes TypeDoc for large or complex private methods.
|
||||||
|
- [ ] My PR **passes all specs/tests and includes new/updated specs or tests** based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||||
|
- [ ] My PR **aligns with [Accessibility guidelines](https://wiki.lyrasis.org/display/DSDOC8x/Accessibility)** if it makes changes to the user interface.
|
||||||
|
- [ ] My PR **uses i18n (internationalization) keys** instead of hardcoded English text, to allow for translations.
|
||||||
|
- [ ] My PR **includes details on how to test it**. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
|
||||||
- [ ] If my PR includes new libraries/dependencies (in `package.json`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
- [ ] If my PR includes new libraries/dependencies (in `package.json`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||||
- [ ] If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself.
|
- [ ] If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself.
|
||||||
- [ ] If my PR fixes an issue ticket, I've [linked them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
- [ ] If my PR fixes an issue ticket, I've [linked them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
||||||
|
87
.github/workflows/build.yml
vendored
87
.github/workflows/build.yml
vendored
@@ -7,7 +7,8 @@ name: Build
|
|||||||
on: [push, pull_request]
|
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,12 +34,15 @@ 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
|
# Project name to use when running "docker compose" prior to e2e tests
|
||||||
COMPOSE_PROJECT_NAME: 'ci'
|
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
|
||||||
@@ -69,51 +73,65 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
google-chrome --version
|
google-chrome --version
|
||||||
|
|
||||||
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
|
# https://github.com/actions/cache/blob/main/examples.md#node---npm
|
||||||
- name: Get Yarn cache directory
|
- name: Get NPM cache directory
|
||||||
id: yarn-cache-dir-path
|
id: npm-cache-dir
|
||||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||||
- name: Cache Yarn dependencies
|
- name: Cache NPM dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
# Cache entire Yarn cache directory (see previous step)
|
# Cache entire NPM cache directory (see previous step)
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
||||||
# Cache key is hash of yarn.lock. Therefore changes to yarn.lock will invalidate cache
|
# Cache key is hash of package-lock.json. Therefore changes to package-lock.json will invalidate cache
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: ${{ runner.os }}-yarn-
|
restore-keys: ${{ runner.os }}-npm-
|
||||||
|
|
||||||
- name: Install Yarn dependencies
|
- name: Install NPM dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: npm clean-install
|
||||||
|
|
||||||
|
- name: Build lint plugins
|
||||||
|
run: npm run build:lint
|
||||||
|
|
||||||
|
- name: Run lint plugin tests
|
||||||
|
run: npm run test:lint:nobuild
|
||||||
|
|
||||||
- name: Run lint
|
- name: Run lint
|
||||||
run: yarn run lint --quiet
|
run: npm run lint:nobuild -- --quiet
|
||||||
|
|
||||||
- name: Check for circular dependencies
|
- name: Check for circular dependencies
|
||||||
run: yarn run check-circ-deps
|
run: npm run check-circ-deps
|
||||||
|
|
||||||
- name: Run build
|
- name: Run build
|
||||||
run: yarn run build:prod
|
run: npm run build:prod
|
||||||
|
|
||||||
- name: Run specs (unit tests)
|
- name: Run specs (unit tests)
|
||||||
run: yarn run test:headless
|
run: npm run test:headless
|
||||||
|
|
||||||
# Upload code coverage report to artifact (for one version of Node only),
|
# Upload code coverage report to artifact (for one version of Node only),
|
||||||
# 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
|
||||||
@@ -125,7 +143,7 @@ jobs:
|
|||||||
# Run tests in Chrome, headless mode (default)
|
# Run tests in Chrome, headless mode (default)
|
||||||
browser: chrome
|
browser: chrome
|
||||||
# Start app before running tests (will be stopped automatically after tests finish)
|
# Start app before running tests (will be stopped automatically after tests finish)
|
||||||
start: yarn run serve:ssr
|
start: npm run serve:ssr
|
||||||
# Wait for backend & frontend to be available
|
# Wait for backend & frontend to be available
|
||||||
# NOTE: We use the 'sites' REST endpoint to also ensure the database is ready
|
# NOTE: We use the 'sites' REST endpoint to also ensure the database is ready
|
||||||
wait-on: http://127.0.0.1:8080/server/api/core/sites, http://127.0.0.1:4000
|
wait-on: http://127.0.0.1:8080/server/api/core/sites, http://127.0.0.1:4000
|
||||||
@@ -135,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)
|
||||||
@@ -161,7 +179,7 @@ jobs:
|
|||||||
# Start up the app with SSR enabled (run in background)
|
# Start up the app with SSR enabled (run in background)
|
||||||
- name: Start app in SSR (server-side rendering) mode
|
- name: Start app in SSR (server-side rendering) mode
|
||||||
run: |
|
run: |
|
||||||
nohup yarn run serve:ssr &
|
nohup npm run serve:ssr &
|
||||||
printf 'Waiting for app to start'
|
printf 'Waiting for app to start'
|
||||||
until curl --output /dev/null --silent --head --fail http://127.0.0.1:4000/home; do
|
until curl --output /dev/null --silent --head --fail http://127.0.0.1:4000/home; do
|
||||||
printf '.'
|
printf '.'
|
||||||
@@ -182,7 +200,7 @@ jobs:
|
|||||||
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.
|
||||||
@@ -197,7 +215,7 @@ jobs:
|
|||||||
|
|
||||||
# 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.
|
||||||
@@ -207,11 +225,12 @@ jobs:
|
|||||||
- name: Upload coverage to Codecov.io
|
- name: Upload coverage to Codecov.io
|
||||||
uses: Wandalen/wretry.action@v1.3.0
|
uses: Wandalen/wretry.action@v1.3.0
|
||||||
with:
|
with:
|
||||||
action: codecov/codecov-action@v3
|
action: codecov/codecov-action@v4
|
||||||
# Ensure codecov-action throws an error when it fails to upload
|
# Ensure codecov-action throws an error when it fails to upload
|
||||||
# This allows us to auto-restart the action if an error is thrown
|
# This allows us to auto-restart the action if an error is thrown
|
||||||
with: |
|
with: |
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
# Try re-running action 5 times max
|
# Try re-running action 5 times max
|
||||||
attempt_limit: 5
|
attempt_limit: 5
|
||||||
# Run again in 30 seconds
|
# Run again in 30 seconds
|
||||||
|
5
.github/workflows/docker.yml
vendored
5
.github/workflows/docker.yml
vendored
@@ -16,7 +16,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
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:
|
||||||
#############################################################
|
#############################################################
|
||||||
@@ -28,7 +29,7 @@ jobs:
|
|||||||
# Use the reusable-docker-build.yml script from DSpace/DSpace repo to build our Docker image
|
# Use the reusable-docker-build.yml script from DSpace/DSpace repo to build our Docker image
|
||||||
uses: DSpace/DSpace/.github/workflows/reusable-docker-build.yml@main
|
uses: DSpace/DSpace/.github/workflows/reusable-docker-build.yml@main
|
||||||
with:
|
with:
|
||||||
build_id: dspace-angular
|
build_id: dspace-angular-dev
|
||||||
image_name: dspace/dspace-angular
|
image_name: dspace/dspace-angular
|
||||||
dockerfile_path: ./Dockerfile
|
dockerfile_path: ./Dockerfile
|
||||||
secrets:
|
secrets:
|
||||||
|
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
|
||||||
|
2
.github/workflows/pull_request_opened.yml
vendored
2
.github/workflows/pull_request_opened.yml
vendored
@@ -21,4 +21,4 @@ jobs:
|
|||||||
# Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
|
# Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
|
||||||
# See https://github.com/toshimaru/auto-author-assign
|
# See https://github.com/toshimaru/auto-author-assign
|
||||||
- name: Assign PR to creator
|
- name: Assign PR to creator
|
||||||
uses: toshimaru/auto-author-assign@v2.0.1
|
uses: toshimaru/auto-author-assign@v2.1.0
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
/.angular/cache
|
/.angular/cache
|
||||||
|
/.nx
|
||||||
/__build__
|
/__build__
|
||||||
/__server_build__
|
/__server_build__
|
||||||
/node_modules
|
/node_modules
|
||||||
@@ -27,12 +28,12 @@ webpack.records.json
|
|||||||
|
|
||||||
morgan.log
|
morgan.log
|
||||||
|
|
||||||
|
# Yarn no longer used
|
||||||
|
yarn.lock
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
|
||||||
*.css
|
*.css
|
||||||
|
|
||||||
package-lock.json
|
|
||||||
|
|
||||||
.java-version
|
.java-version
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
@@ -10,7 +10,7 @@ DSpace is a community built and supported project. We do not have a centralized
|
|||||||
## Contribute new code via a Pull Request
|
## Contribute new code via a Pull Request
|
||||||
|
|
||||||
We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone.
|
We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone.
|
||||||
Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC7x/Release+Notes).
|
Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC8x/Release+Notes).
|
||||||
|
|
||||||
Code Contribution Checklist
|
Code Contribution Checklist
|
||||||
- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests)
|
- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests)
|
||||||
@@ -18,6 +18,9 @@ Code Contribution Checklist
|
|||||||
- [ ] PRs **must** not introduce circular dependencies (verified via `yarn check-circ-deps`)
|
- [ ] PRs **must** not introduce circular dependencies (verified via `yarn check-circ-deps`)
|
||||||
- [ ] PRs **must** include [TypeDoc](https://typedoc.org/) comments for _all new (or modified) public methods and classes_. Large or complex private methods should also have TypeDoc.
|
- [ ] PRs **must** include [TypeDoc](https://typedoc.org/) comments for _all new (or modified) public methods and classes_. Large or complex private methods should also have TypeDoc.
|
||||||
- [ ] PRs **must** pass all automated pecs/tests and includes new/updated specs or tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
- [ ] PRs **must** pass all automated pecs/tests and includes new/updated specs or tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||||
|
- [ ] User interface changes **must** align with [Accessibility guidelines](https://wiki.lyrasis.org/display/DSDOC8x/Accessibility)
|
||||||
|
- [ ] PRs **must** use i18n (internationalization) keys instead of hardcoded English text, to allow for translations.
|
||||||
|
- [ ] Details on how to test the PR **must** be provided. Reviewers must be aware of any steps they need to take to successfully test your fix or feature.
|
||||||
- [ ] If a PR includes new libraries/dependencies (in `package.json`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/dspace-angular/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
- [ ] If a PR includes new libraries/dependencies (in `package.json`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/dspace-angular/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||||
- [ ] Basic technical documentation _should_ be provided for any new features or configuration, either in the PR itself or in the DSpace Wiki documentation.
|
- [ ] Basic technical documentation _should_ be provided for any new features or configuration, either in the PR itself or in the DSpace Wiki documentation.
|
||||||
- [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
- [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
||||||
@@ -26,7 +29,7 @@ Additional details on the code contribution process can be found in our [Code Co
|
|||||||
|
|
||||||
## Contribute documentation
|
## Contribute documentation
|
||||||
|
|
||||||
DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x
|
DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC
|
||||||
|
|
||||||
If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org.
|
If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org.
|
||||||
Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation.
|
Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation.
|
||||||
@@ -34,7 +37,7 @@ Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyra
|
|||||||
## Help others on mailing lists or Slack
|
## Help others on mailing lists or Slack
|
||||||
|
|
||||||
DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered.
|
DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered.
|
||||||
Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via LYRASIS).
|
Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via Lyrasis).
|
||||||
|
|
||||||
## Join a working or interest group
|
## Join a working or interest group
|
||||||
|
|
||||||
@@ -42,5 +45,5 @@ Most of the work in building/improving DSpace comes via [Working Groups](https:/
|
|||||||
|
|
||||||
All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include:
|
All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include:
|
||||||
|
|
||||||
* [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) - This is the main (mostly volunteer) development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs.
|
* [DSpace Developer Team](https://wiki.lyrasis.org/display/DSPACE/Developer+Meetings) - This is the primary, volunteer development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs. This is also were discussions of the next release or major issues occur. Anyone is welcome to attend.
|
||||||
* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers.
|
* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers. Anyone is welcome to attend.
|
||||||
|
10
Dockerfile
10
Dockerfile
@@ -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
|
||||||
@@ -11,9 +11,7 @@ WORKDIR /app
|
|||||||
ADD . /app/
|
ADD . /app/
|
||||||
EXPOSE 4000
|
EXPOSE 4000
|
||||||
|
|
||||||
# We run yarn install with an increased network timeout (5min) to avoid "ESOCKETTIMEDOUT" errors from hub.docker.com
|
RUN npm install
|
||||||
# See, for example https://github.com/yarnpkg/yarn/issues/5540
|
|
||||||
RUN yarn install --network-timeout 300000
|
|
||||||
|
|
||||||
# When running in dev mode, 4GB of memory is required to build & launch the app.
|
# When running in dev mode, 4GB of memory is required to build & launch the app.
|
||||||
# This default setting can be overridden as needed in your shell, via an env file or in docker-compose.
|
# This default setting can be overridden as needed in your shell, via an env file or in docker-compose.
|
||||||
@@ -24,5 +22,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 npm run serve -- --host 0.0.0.0
|
||||||
|
@@ -4,18 +4,18 @@
|
|||||||
# Test build:
|
# Test build:
|
||||||
# docker build -f Dockerfile.dist -t dspace/dspace-angular:latest-dist .
|
# docker build -f Dockerfile.dist -t dspace/dspace-angular:latest-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
|
||||||
RUN apk add --update python3 make g++ && rm -rf /var/cache/apk/*
|
RUN apk add --update python3 make g++ && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json package-lock.json ./
|
||||||
RUN yarn install --network-timeout 300000
|
RUN npm install
|
||||||
|
|
||||||
ADD . /app/
|
ADD . /app/
|
||||||
RUN yarn build:prod
|
RUN npm run build:prod
|
||||||
|
|
||||||
FROM node:18-alpine
|
FROM node:18-alpine
|
||||||
RUN npm install --global pm2
|
RUN npm install --global pm2
|
||||||
@@ -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
|
||||||
|
82
README.md
82
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`**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# clone the repo
|
# clone the repo
|
||||||
@@ -45,10 +45,10 @@ git clone https://github.com/DSpace/dspace-angular.git
|
|||||||
cd dspace-angular
|
cd dspace-angular
|
||||||
|
|
||||||
# install the local dependencies
|
# install the local dependencies
|
||||||
yarn install
|
npm install
|
||||||
|
|
||||||
# start the server
|
# start the server
|
||||||
yarn start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Then go to [http://localhost:4000](http://localhost:4000) in your browser
|
Then go to [http://localhost:4000](http://localhost:4000) in your browser
|
||||||
@@ -77,7 +77,7 @@ Table of Contents
|
|||||||
- [Recommended Editors/IDEs](#recommended-editorsides)
|
- [Recommended Editors/IDEs](#recommended-editorsides)
|
||||||
- [Collaborating](#collaborating)
|
- [Collaborating](#collaborating)
|
||||||
- [File Structure](#file-structure)
|
- [File Structure](#file-structure)
|
||||||
- [Managing Dependencies (via yarn)](#managing-dependencies-via-yarn)
|
- [Managing Dependencies (via npm)](#managing-dependencies-via-npm)
|
||||||
- [Frequently asked questions](#frequently-asked-questions)
|
- [Frequently asked questions](#frequently-asked-questions)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
@@ -89,15 +89,15 @@ You can find more information on the technologies used in this project (Angular.
|
|||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org) and [yarn](https://yarnpkg.com)
|
- [Node.js](https://nodejs.org)
|
||||||
- Ensure you're running node `v16.x` or `v18.x` and yarn == `v1.x`
|
- Ensure you're running node `v18.x` or `v20.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.
|
||||||
|
|
||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
|
|
||||||
- `yarn install` to install the local dependencies
|
- `npm install` to install the local dependencies
|
||||||
|
|
||||||
### Configuring
|
### Configuring
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ import { environment } from '../environment.ts';
|
|||||||
Running the app
|
Running the app
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
After you have installed all dependencies you can now run the app. Run `yarn run start:dev` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:4000`.
|
After you have installed all dependencies you can now run the app. Run `npm run start:dev` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:4000`.
|
||||||
|
|
||||||
### Running in production mode
|
### Running in production mode
|
||||||
|
|
||||||
@@ -211,20 +211,20 @@ When building for production we're using Ahead of Time (AoT) compilation. With A
|
|||||||
To build the app for production and start the server (in one command) run:
|
To build the app for production and start the server (in one command) run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn start
|
npm start
|
||||||
```
|
```
|
||||||
This will run the application in an instance of the Express server, which is included.
|
This will run the application in an instance of the Express server, which is included.
|
||||||
|
|
||||||
If you only want to build for production, without starting, run:
|
If you only want to build for production, without starting, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn run build:prod
|
npm run build:prod
|
||||||
```
|
```
|
||||||
This will build the application and put the result in the `dist` folder. You can copy this folder to wherever you need it for your application server. If you will be using the built-in Express server, you'll also need a copy of the `node_modules` folder tucked inside your copy of `dist`.
|
This will build the application and put the result in the `dist` folder. You can copy this folder to wherever you need it for your application server. If you will be using the built-in Express server, you'll also need a copy of the `node_modules` folder tucked inside your copy of `dist`.
|
||||||
|
|
||||||
After building the app for production, it can be started by running:
|
After building the app for production, it can be started by running:
|
||||||
```bash
|
```bash
|
||||||
yarn run serve:ssr
|
npm run serve:ssr
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running the application with Docker
|
### Running the application with Docker
|
||||||
@@ -238,14 +238,14 @@ Cleaning
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# clean everything, including node_modules. You'll need to run yarn install again afterwards.
|
# clean everything, including node_modules. You'll need to run npm install again afterwards.
|
||||||
yarn run clean
|
npm run clean
|
||||||
|
|
||||||
# clean files generated by the production build (.ngfactory files, css files, etc)
|
# clean files generated by the production build (.ngfactory files, css files, etc)
|
||||||
yarn run clean:prod
|
npm run clean:prod
|
||||||
|
|
||||||
# cleans the distribution directory
|
# cleans the distribution directory
|
||||||
yarn run clean:dist
|
npm run clean:dist
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -259,9 +259,9 @@ If you would like to contribute by testing a Pull Request (PR), here's how to do
|
|||||||
1. Pull down the branch that the Pull Request was built from. Easy instructions for doing so can be found on the Pull Request itself.
|
1. Pull down the branch that the Pull Request was built from. Easy instructions for doing so can be found on the Pull Request itself.
|
||||||
* Next to the "Merge" button, you'll see a link that says "command line instructions".
|
* Next to the "Merge" button, you'll see a link that says "command line instructions".
|
||||||
* Click it, and follow "Step 1" of those instructions to checkout the pull down the PR branch.
|
* Click it, and follow "Step 1" of those instructions to checkout the pull down the PR branch.
|
||||||
2. `yarn run clean` (This resets your local dependencies to ensure you are up-to-date with this PR)
|
2. `npm run clean` (This resets your local dependencies to ensure you are up-to-date with this PR)
|
||||||
3. `yarn install` (Updates your local dependencies to those in the PR)
|
3. `npm install` (Updates your local dependencies to those in the PR)
|
||||||
4. `yarn start` (Rebuilds the project, and deploys to localhost:4000, by default)
|
4. `npm start` (Rebuilds the project, and deploys to localhost:4000, by default)
|
||||||
5. At this point, the code from the PR will be deployed to http://localhost:4000. Test it out, and ensure that it does what is described in the PR (or fixes the bug described in the ticket linked to the PR).
|
5. At this point, the code from the PR will be deployed to http://localhost:4000. Test it out, and ensure that it does what is described in the PR (or fixes the bug described in the ticket linked to the PR).
|
||||||
|
|
||||||
Once you have tested the Pull Request, please add a comment and/or approval to the PR to let us know whether you found it to be successful (or not). Thanks!
|
Once you have tested the Pull Request, please add a comment and/or approval to the PR to let us know whether you found it to be successful (or not). Thanks!
|
||||||
@@ -271,13 +271,13 @@ Once you have tested the Pull Request, please add a comment and/or approval to t
|
|||||||
|
|
||||||
Unit tests use the [Jasmine test framework](https://jasmine.github.io/), and are run via [Karma](https://karma-runner.github.io/).
|
Unit tests use the [Jasmine test framework](https://jasmine.github.io/), and are run via [Karma](https://karma-runner.github.io/).
|
||||||
|
|
||||||
You can find the Karma configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test environment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `yarn run coverage`.
|
You can find the Karma configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test environment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `npm run coverage`.
|
||||||
|
|
||||||
The default browser is Google Chrome.
|
The default browser is Google Chrome.
|
||||||
|
|
||||||
Place your tests in the same location of the application source code files that they test, e.g. ending with `*.component.spec.ts`
|
Place your tests in the same location of the application source code files that they test, e.g. ending with `*.component.spec.ts`
|
||||||
|
|
||||||
and run: `yarn test`
|
and run: `npm test`
|
||||||
|
|
||||||
If you run into odd test errors, see the Angular guide to debugging tests: https://angular.io/guide/test-debugging
|
If you run into odd test errors, see the Angular guide to debugging tests: https://angular.io/guide/test-debugging
|
||||||
|
|
||||||
@@ -357,14 +357,14 @@ Some UI specific configuration documentation is also found in the [`./docs`](doc
|
|||||||
|
|
||||||
To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts information from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
|
To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts information from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
|
||||||
|
|
||||||
Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder.
|
Run:`npm run docs` to produce the documentation that will be available in the 'doc' folder.
|
||||||
|
|
||||||
Other commands
|
Other commands
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
There are many more commands in the `scripts` section of `package.json`. Most of these are executed by one of the commands mentioned above.
|
There are many more commands in the `scripts` section of `package.json`. Most of these are executed by one of the commands mentioned above.
|
||||||
|
|
||||||
A command with a name that starts with `pre` or `post` will be executed automatically before or after the script with the matching name. e.g. if you type `yarn run start` the `prestart` script will run first, then the `start` script will trigger.
|
A command with a name that starts with `pre` or `post` will be executed automatically before or after the script with the matching name. e.g. if you type `npm run start` the `prestart` script will run first, then the `start` script will trigger.
|
||||||
|
|
||||||
Recommended Editors/IDEs
|
Recommended Editors/IDEs
|
||||||
------------------------
|
------------------------
|
||||||
@@ -456,6 +456,7 @@ dspace-angular
|
|||||||
├── LICENSES_THIRD_PARTY *
|
├── LICENSES_THIRD_PARTY *
|
||||||
├── nodemon.json * Nodemon (https://nodemon.io/) configuration
|
├── nodemon.json * Nodemon (https://nodemon.io/) configuration
|
||||||
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc.
|
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc.
|
||||||
|
├── package-lock.json * npm lockfile (https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json)
|
||||||
├── postcss.config.js * PostCSS (http://postcss.org/) configuration
|
├── postcss.config.js * PostCSS (http://postcss.org/) configuration
|
||||||
├── README.md * This document
|
├── README.md * This document
|
||||||
├── SECURITY.md *
|
├── SECURITY.md *
|
||||||
@@ -466,30 +467,29 @@ dspace-angular
|
|||||||
├── tsconfig.spec.json * TypeScript config for tests
|
├── tsconfig.spec.json * TypeScript config for tests
|
||||||
├── tsconfig.ts-node.json * TypeScript config for using ts-node directly
|
├── tsconfig.ts-node.json * TypeScript config for using ts-node directly
|
||||||
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
|
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
|
||||||
├── typedoc.json * TYPEDOC configuration
|
└── typedoc.json * TYPEDOC configuration
|
||||||
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Managing Dependencies (via yarn)
|
Managing Dependencies (via npm)
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
This project makes use of [`yarn`](https://yarnpkg.com/en/) to ensure that the exact same dependency versions are used every time you install it.
|
This project makes use of [`npm`](https://docs.npmjs.com/about-npm) to ensure that the exact same dependency versions are used every time you install it.
|
||||||
|
|
||||||
* `yarn` creates a [`yarn.lock`](https://yarnpkg.com/en/docs/yarn-lock) to track those versions. That file is updated automatically by whenever dependencies are added/updated/removed via yarn.
|
* `npm` creates a [`package-lock.json`](https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json) to track those versions. That file is updated automatically by whenever dependencies are added/updated/removed via npm.
|
||||||
* **Adding new dependencies**: To install/add a new dependency (third party library), use [`yarn add`](https://yarnpkg.com/en/docs/cli/add). For example: `yarn add some-lib`.
|
* **Adding new dependencies**: To install/add a new dependency (third party library), use [`npm install`](https://docs.npmjs.com/cli/v10/commands/npm-install). For example: `npm install some-lib`.
|
||||||
* If you are adding a new build tool dependency (to `devDependencies`), use `yarn add some-lib --dev`
|
* If you are adding a new build tool dependency (to `devDependencies`), use `npm install some-lib --save--dev`
|
||||||
* **Upgrading existing dependencies**: To upgrade existing dependencies, you can use [`yarn upgrade`](https://yarnpkg.com/en/docs/cli/upgrade). For example: `yarn upgrade some-lib` or `yarn upgrade some-lib@version`
|
* **Upgrading existing dependencies**: To upgrade existing dependencies, you can use [`npm update`](https://docs.npmjs.com/cli/v10/commands/npm-update). For example: `npm update some-lib` or `npm update some-lib@version`
|
||||||
* **Removing dependencies**: If a dependency is no longer needed, or replaced, use [`yarn remove`](https://yarnpkg.com/en/docs/cli/remove) to remove it.
|
* **Removing dependencies**: If a dependency is no longer needed, or replaced, use [`npm uninstall`](https://docs.npmjs.com/cli/v10/commands/npm-uninstall) to remove it.
|
||||||
|
|
||||||
As you can see above, using `yarn` commandline tools means that you should never need to modify the `package.json` manually. *We recommend always using `yarn` to keep dependencies updated / in sync.*
|
As you can see above, using `npm` commandline tools means that you should never need to modify the `package.json` manually. *We recommend always using `npm` to keep dependencies updated / in sync.*
|
||||||
|
|
||||||
### Adding Typings for libraries
|
### Adding Typings for libraries
|
||||||
|
|
||||||
If the library does not include typings, you can install them using yarn:
|
If the library does not include typings, you can install them using npm:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn add d3
|
npm install d3
|
||||||
yarn add @types/d3 --dev
|
npm install @types/d3 --save-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
If the library doesn't have typings available at `@types/`, you can still use it by manually adding typings for it:
|
If the library doesn't have typings available at `@types/`, you can still use it by manually adding typings for it:
|
||||||
@@ -527,13 +527,13 @@ Frequently asked questions
|
|||||||
- What are the naming conventions for Angular?
|
- What are the naming conventions for Angular?
|
||||||
- See [the official angular style guide](https://angular.io/styleguide)
|
- See [the official angular style guide](https://angular.io/styleguide)
|
||||||
- Why is the size of my app larger in development?
|
- Why is the size of my app larger in development?
|
||||||
- The production build uses a whole host of techniques (ahead-of-time compilation, rollup to remove unreachable code, minification, etc.) to reduce the size, that aren't used during development in the intrest of build speed.
|
- The production build uses a whole host of techniques (ahead-of-time compilation, rollup to remove unreachable code, minification, etc.) to reduce the size, that aren't used during development in the interest of build speed.
|
||||||
- node-pre-gyp ERR in yarn install (Windows)
|
- node-pre-gyp ERR in npm install (Windows)
|
||||||
- install Python x86 version between 2.5 and 3.0 on windows. See [this issue](https://github.com/AngularClass/angular2-webpack-starter/issues/626)
|
- install Python x86 version between 2.5 and 3.0 on windows. See [this issue](https://github.com/AngularClass/angular2-webpack-starter/issues/626)
|
||||||
- How do I handle merge conflicts in yarn.lock?
|
- How do I handle merge conflicts in package-lock.json?
|
||||||
- first check out the yarn.lock file from the branch you're merging in to yours: e.g. `git checkout --theirs yarn.lock`
|
- first check out the package-lock.json file from the branch you're merging in to yours: e.g. `git checkout --theirs package-lock.json`
|
||||||
- now run `yarn install` again. Yarn will create a new lockfile that contains both sets of changes.
|
- now run `npm install` again. NPM will create a new lockfile that contains both sets of changes.
|
||||||
- then run `git add yarn.lock` to stage the lockfile for commit
|
- then run `git add package-lock.json` to stage the lockfile for commit
|
||||||
- and `git commit` to conclude the merge
|
- and `git commit` to conclude the merge
|
||||||
|
|
||||||
Getting Help
|
Getting Help
|
||||||
|
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,6 +17,23 @@ 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
|
||||||
|
# Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects.
|
||||||
|
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ]
|
||||||
|
# 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,
|
||||||
|
|
||||||
# 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.
|
||||||
@@ -52,7 +69,7 @@ cache:
|
|||||||
# Set to true to see all cache hits/misses/refreshes in your console logs. Useful for debugging SSR caching issues.
|
# Set to true to see all cache hits/misses/refreshes in your console logs. Useful for debugging SSR caching issues.
|
||||||
debug: false
|
debug: false
|
||||||
# When enabled (i.e. max > 0), known bots will be sent pages from a server side cache specific for bots.
|
# When enabled (i.e. max > 0), known bots will be sent pages from a server side cache specific for bots.
|
||||||
# (Keep in mind, bot detection cannot be guarranteed. It is possible some bots will bypass this cache.)
|
# (Keep in mind, bot detection cannot be guaranteed. It is possible some bots will bypass this cache.)
|
||||||
botCache:
|
botCache:
|
||||||
# Maximum number of pages to cache for known bots. Set to zero (0) to disable server side caching for bots.
|
# Maximum number of pages to cache for known bots. Set to zero (0) to disable server side caching for bots.
|
||||||
# Default is 1000, which means the 1000 most recently accessed public pages will be cached.
|
# Default is 1000, which means the 1000 most recently accessed public pages will be cached.
|
||||||
@@ -195,6 +212,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
|
||||||
@@ -204,24 +227,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
|
||||||
@@ -237,8 +272,8 @@ languages:
|
|||||||
- code: sr-lat
|
- code: sr-lat
|
||||||
label: Srpski (lat)
|
label: Srpski (lat)
|
||||||
active: true
|
active: true
|
||||||
- code: fi
|
- code: sr-cyr
|
||||||
label: Suomi
|
label: Српски
|
||||||
active: true
|
active: true
|
||||||
- code: sv
|
- code: sv
|
||||||
label: Svenska
|
label: Svenska
|
||||||
@@ -246,27 +281,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: sr-cyr
|
|
||||||
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
|
||||||
@@ -400,10 +420,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.
|
||||||
@@ -437,6 +458,12 @@ search:
|
|||||||
enabled: false
|
enabled: false
|
||||||
# List of filters to enable in "Advanced Search" dropdown
|
# List of filters to enable in "Advanced Search" dropdown
|
||||||
filter: [ 'title', 'author', 'subject', 'entityType' ]
|
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
|
# Notify metrics
|
||||||
@@ -492,6 +519,16 @@ notifyMetrics:
|
|||||||
description: 'admin-notify-dashboard.NOTIFY.outgoing.delivered.description'
|
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,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',
|
||||||
@@ -18,6 +19,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
// 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',
|
||||||
@@ -33,6 +35,8 @@ export default defineConfig({
|
|||||||
// 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('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('[data-test="admin-menu-section-new-title"]').should('be.visible');
|
||||||
|
cy.get('[data-test="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('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('[data-test="admin-menu-section-new-title"]').should('be.visible');
|
||||||
|
cy.get('[data-test="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('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('[data-test="admin-menu-section-new-title"]').should('be.visible');
|
||||||
|
cy.get('[data-test="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('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('[data-test="admin-menu-section-edit-title"]').should('be.visible');
|
||||||
|
cy.get('[data-test="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('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('[data-test="admin-menu-section-edit-title"]').should('be.visible');
|
||||||
|
cy.get('[data-test="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('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('[data-test="admin-menu-section-edit-title"]').should('be.visible');
|
||||||
|
cy.get('[data-test="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('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('[data-test="admin-menu-section-export-title"]').should('be.visible');
|
||||||
|
cy.get('[data-test="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('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
|
// Click on entry of menu
|
||||||
|
cy.get('[data-test="admin-menu-section-export-title"]').should('be.visible');
|
||||||
|
cy.get('[data-test="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');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,28 +1,28 @@
|
|||||||
import { Options } from 'cypress-axe';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
import { Options } from 'cypress-axe';
|
||||||
|
|
||||||
describe('Admin Sidebar', () => {
|
describe('Admin Sidebar', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Must login as an Admin for sidebar to appear
|
// Must login as an Admin for sidebar to appear
|
||||||
cy.visit('/login');
|
cy.visit('/login');
|
||||||
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be pinnable and pass accessibility tests', () => {
|
it('should be pinnable and pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('#sidebar-collapse-toggle').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on every expandable section to open all menus
|
// Click on every expandable section to open all menus
|
||||||
cy.get('ds-expandable-admin-sidebar-section').click({multiple: true});
|
cy.get('ds-expandable-admin-sidebar-section').click({ multiple: true });
|
||||||
|
|
||||||
// Analyze <ds-admin-sidebar> for accessibility
|
// Analyze <ds-admin-sidebar> for accessibility
|
||||||
testA11y('ds-admin-sidebar',
|
testA11y('ds-admin-sidebar',
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
// Currently all expandable sections have nested interactive elements
|
// Currently all expandable sections have nested interactive elements
|
||||||
// See https://github.com/DSpace/dspace-angular/issues/2178
|
// See https://github.com/DSpace/dspace-angular/issues/2178
|
||||||
'nested-interactive': { enabled: false },
|
'nested-interactive': { enabled: false },
|
||||||
}
|
},
|
||||||
} as Options);
|
} 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,14 +1,14 @@
|
|||||||
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(Cypress.env('DSPACE_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');
|
||||||
|
|
||||||
// Analyze <ds-breadcrumbs> for accessibility
|
// Analyze <ds-breadcrumbs> for accessibility
|
||||||
testA11y('ds-breadcrumbs');
|
testA11y('ds-breadcrumbs');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Browse By Author', () => {
|
describe('Browse By Author', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
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').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');
|
testA11y('ds-browse-by-metadata');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Browse By Date Issued', () => {
|
describe('Browse By Date Issued', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
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').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');
|
testA11y('ds-browse-by-date');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Browse By Subject', () => {
|
describe('Browse By Subject', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
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').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');
|
testA11y('ds-browse-by-metadata');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Browse By Title', () => {
|
describe('Browse By Title', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
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').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');
|
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');
|
||||||
|
});
|
@@ -3,126 +3,126 @@ import { testA11y } from 'cypress/support/utils';
|
|||||||
const COLLECTION_EDIT_PAGE = '/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('/edit');
|
const COLLECTION_EDIT_PAGE = '/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('/edit');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// All tests start with visiting the Edit Collection Page
|
// All tests start with visiting the Edit Collection Page
|
||||||
cy.visit(COLLECTION_EDIT_PAGE);
|
cy.visit(COLLECTION_EDIT_PAGE);
|
||||||
|
|
||||||
// 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(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Collection > Edit Metadata tab', () => {
|
describe('Edit Collection > Edit Metadata tab', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
// <ds-edit-collection> tag must be loaded
|
// <ds-edit-collection> tag must be loaded
|
||||||
cy.get('ds-edit-collection').should('be.visible');
|
cy.get('ds-edit-collection').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-edit-collection> for accessibility issues
|
// Analyze <ds-edit-collection> for accessibility issues
|
||||||
testA11y('ds-edit-collection');
|
testA11y('ds-edit-collection');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Collection > Assign Roles tab', () => {
|
describe('Edit Collection > Assign Roles tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="roles"]').click();
|
cy.get('a[data-test="roles"]').click();
|
||||||
|
|
||||||
// <ds-collection-roles> tag must be loaded
|
// <ds-collection-roles> tag must be loaded
|
||||||
cy.get('ds-collection-roles').should('be.visible');
|
cy.get('ds-collection-roles').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-collection-roles');
|
testA11y('ds-collection-roles');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Collection > Content Source tab', () => {
|
describe('Edit Collection > Content Source tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="source"]').click();
|
cy.get('a[data-test="source"]').click();
|
||||||
|
|
||||||
// <ds-collection-source> tag must be loaded
|
// <ds-collection-source> tag must be loaded
|
||||||
cy.get('ds-collection-source').should('be.visible');
|
cy.get('ds-collection-source').should('be.visible');
|
||||||
|
|
||||||
// Check the external source checkbox (to display all fields on the page)
|
// Check the external source checkbox (to display all fields on the page)
|
||||||
cy.get('#externalSourceCheck').check();
|
cy.get('#externalSourceCheck').check();
|
||||||
|
|
||||||
// Wait for the source controls to appear
|
// Wait for the source controls to appear
|
||||||
// cy.get('ds-collection-source-controls').should('be.visible');
|
// cy.get('ds-collection-source-controls').should('be.visible');
|
||||||
|
|
||||||
// Analyze entire page for accessibility issues
|
// Analyze entire page for accessibility issues
|
||||||
testA11y('ds-collection-source');
|
testA11y('ds-collection-source');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Collection > Curate tab', () => {
|
describe('Edit Collection > Curate tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="curate"]').click();
|
cy.get('a[data-test="curate"]').click();
|
||||||
|
|
||||||
// <ds-collection-curate> tag must be loaded
|
// <ds-collection-curate> tag must be loaded
|
||||||
cy.get('ds-collection-curate').should('be.visible');
|
cy.get('ds-collection-curate').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-collection-curate');
|
testA11y('ds-collection-curate');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Collection > Access Control tab', () => {
|
describe('Edit Collection > Access Control tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="access-control"]').click();
|
cy.get('a[data-test="access-control"]').click();
|
||||||
|
|
||||||
// <ds-collection-access-control> tag must be loaded
|
// <ds-collection-access-control> tag must be loaded
|
||||||
cy.get('ds-collection-access-control').should('be.visible');
|
cy.get('ds-collection-access-control').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-collection-access-control');
|
testA11y('ds-collection-access-control');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Collection > Authorizations tab', () => {
|
describe('Edit Collection > Authorizations tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="authorizations"]').click();
|
cy.get('a[data-test="authorizations"]').click();
|
||||||
|
|
||||||
// <ds-collection-authorizations> tag must be loaded
|
// <ds-collection-authorizations> tag must be loaded
|
||||||
cy.get('ds-collection-authorizations').should('be.visible');
|
cy.get('ds-collection-authorizations').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-collection-authorizations');
|
testA11y('ds-collection-authorizations');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Collection > Item Mapper tab', () => {
|
describe('Edit Collection > Item Mapper tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="mapper"]').click();
|
cy.get('a[data-test="mapper"]').click();
|
||||||
|
|
||||||
// <ds-collection-item-mapper> tag must be loaded
|
// <ds-collection-item-mapper> tag must be loaded
|
||||||
cy.get('ds-collection-item-mapper').should('be.visible');
|
cy.get('ds-collection-item-mapper').should('be.visible');
|
||||||
|
|
||||||
// Analyze entire page for accessibility issues
|
// Analyze entire page for accessibility issues
|
||||||
testA11y('ds-collection-item-mapper');
|
testA11y('ds-collection-item-mapper');
|
||||||
|
|
||||||
// Click on the "Map new Items" tab
|
// Click on the "Map new Items" tab
|
||||||
cy.get('li[data-test="mapTab"] a').click();
|
cy.get('li[data-test="mapTab"] a').click();
|
||||||
|
|
||||||
// Make sure search form is now visible
|
// Make sure search form is now visible
|
||||||
cy.get('ds-search-form').should('be.visible');
|
cy.get('ds-search-form').should('be.visible');
|
||||||
|
|
||||||
// Analyze entire page (again) for accessibility issues
|
// Analyze entire page (again) for accessibility issues
|
||||||
testA11y('ds-collection-item-mapper');
|
testA11y('ds-collection-item-mapper');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('Edit Collection > Delete page', () => {
|
describe('Edit Collection > Delete page', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="delete-button"]').click();
|
cy.get('a[data-test="delete-button"]').click();
|
||||||
|
|
||||||
// <ds-delete-collection> tag must be loaded
|
// <ds-delete-collection> tag must be loaded
|
||||||
cy.get('ds-delete-collection').should('be.visible');
|
cy.get('ds-delete-collection').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-delete-collection');
|
testA11y('ds-delete-collection');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,13 +2,13 @@ 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(Cypress.env('DSPACE_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');
|
||||||
|
|
||||||
// Analyze <ds-collection-page> for accessibility issues
|
// Analyze <ds-collection-page> for accessibility issues
|
||||||
testA11y('ds-collection-page');
|
testA11y('ds-collection-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,36 +2,36 @@ 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(Cypress.env('DSPACE_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(Cypress.env('DSPACE_TEST_COLLECTION')));
|
cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')));
|
||||||
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
cy.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);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a "Total visits" section', () => {
|
it('should contain a "Total visits" section', () => {
|
||||||
cy.visit(COLLECTIONSTATISTICSPAGE);
|
cy.visit(COLLECTIONSTATISTICSPAGE);
|
||||||
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
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(Cypress.env('DSPACE_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', () => {
|
||||||
cy.visit(COLLECTIONSTATISTICSPAGE);
|
cy.visit(COLLECTIONSTATISTICSPAGE);
|
||||||
|
|
||||||
// <ds-collection-statistics-page> tag must be loaded
|
// <ds-collection-statistics-page> tag must be loaded
|
||||||
cy.get('ds-collection-statistics-page').should('be.visible');
|
cy.get('ds-collection-statistics-page').should('be.visible');
|
||||||
|
|
||||||
// Verify / wait until "Total Visits" table's label is non-empty
|
// Verify / wait until "Total Visits" table's label is non-empty
|
||||||
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
||||||
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
||||||
|
|
||||||
// Analyze <ds-collection-statistics-page> for accessibility issues
|
// Analyze <ds-collection-statistics-page> for accessibility issues
|
||||||
testA11y('ds-collection-statistics-page');
|
testA11y('ds-collection-statistics-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
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');
|
||||||
|
});
|
@@ -3,84 +3,84 @@ import { testA11y } from 'cypress/support/utils';
|
|||||||
const COMMUNITY_EDIT_PAGE = '/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')).concat('/edit');
|
const COMMUNITY_EDIT_PAGE = '/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')).concat('/edit');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// All tests start with visiting the Edit Community Page
|
// All tests start with visiting the Edit Community Page
|
||||||
cy.visit(COMMUNITY_EDIT_PAGE);
|
cy.visit(COMMUNITY_EDIT_PAGE);
|
||||||
|
|
||||||
// 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(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Community > Edit Metadata tab', () => {
|
describe('Edit Community > Edit Metadata tab', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
// <ds-edit-community> tag must be loaded
|
// <ds-edit-community> tag must be loaded
|
||||||
cy.get('ds-edit-community').should('be.visible');
|
cy.get('ds-edit-community').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-edit-community> for accessibility issues
|
// Analyze <ds-edit-community> for accessibility issues
|
||||||
testA11y('ds-edit-community');
|
testA11y('ds-edit-community');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Community > Assign Roles tab', () => {
|
describe('Edit Community > Assign Roles tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="roles"]').click();
|
cy.get('a[data-test="roles"]').click();
|
||||||
|
|
||||||
// <ds-community-roles> tag must be loaded
|
// <ds-community-roles> tag must be loaded
|
||||||
cy.get('ds-community-roles').should('be.visible');
|
cy.get('ds-community-roles').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-community-roles');
|
testA11y('ds-community-roles');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Community > Curate tab', () => {
|
describe('Edit Community > Curate tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="curate"]').click();
|
cy.get('a[data-test="curate"]').click();
|
||||||
|
|
||||||
// <ds-community-curate> tag must be loaded
|
// <ds-community-curate> tag must be loaded
|
||||||
cy.get('ds-community-curate').should('be.visible');
|
cy.get('ds-community-curate').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-community-curate');
|
testA11y('ds-community-curate');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Community > Access Control tab', () => {
|
describe('Edit Community > Access Control tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="access-control"]').click();
|
cy.get('a[data-test="access-control"]').click();
|
||||||
|
|
||||||
// <ds-community-access-control> tag must be loaded
|
// <ds-community-access-control> tag must be loaded
|
||||||
cy.get('ds-community-access-control').should('be.visible');
|
cy.get('ds-community-access-control').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-community-access-control');
|
testA11y('ds-community-access-control');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Community > Authorizations tab', () => {
|
describe('Edit Community > Authorizations tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="authorizations"]').click();
|
cy.get('a[data-test="authorizations"]').click();
|
||||||
|
|
||||||
// <ds-community-authorizations> tag must be loaded
|
// <ds-community-authorizations> tag must be loaded
|
||||||
cy.get('ds-community-authorizations').should('be.visible');
|
cy.get('ds-community-authorizations').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-community-authorizations');
|
testA11y('ds-community-authorizations');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Community > Delete page', () => {
|
describe('Edit Community > Delete page', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="delete-button"]').click();
|
cy.get('a[data-test="delete-button"]').click();
|
||||||
|
|
||||||
// <ds-delete-community> tag must be loaded
|
// <ds-delete-community> tag must be loaded
|
||||||
cy.get('ds-delete-community').should('be.visible');
|
cy.get('ds-delete-community').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Analyze for accessibility issues
|
||||||
testA11y('ds-delete-community');
|
testA11y('ds-delete-community');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,16 +2,16 @@ import { testA11y } from 'cypress/support/utils';
|
|||||||
|
|
||||||
describe('Community List Page', () => {
|
describe('Community List Page', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit('/community-list');
|
cy.visit('/community-list');
|
||||||
|
|
||||||
// <ds-community-list-page> tag must be loaded
|
// <ds-community-list-page> tag must be loaded
|
||||||
cy.get('ds-community-list-page').should('be.visible');
|
cy.get('ds-community-list-page').should('be.visible');
|
||||||
|
|
||||||
// Open every expand button on page, so that we can scan sub-elements as well
|
// Open every expand button on page, so that we can scan sub-elements as well
|
||||||
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
|
||||||
testA11y('ds-community-list-page');
|
testA11y('ds-community-list-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,13 +2,13 @@ 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(Cypress.env('DSPACE_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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,36 +2,36 @@ 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(Cypress.env('DSPACE_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(Cypress.env('DSPACE_TEST_COMMUNITY')));
|
cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')));
|
||||||
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
cy.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);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a "Total visits" section', () => {
|
it('should contain a "Total visits" section', () => {
|
||||||
cy.visit(COMMUNITYSTATISTICSPAGE);
|
cy.visit(COMMUNITYSTATISTICSPAGE);
|
||||||
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
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(Cypress.env('DSPACE_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', () => {
|
||||||
cy.visit(COMMUNITYSTATISTICSPAGE);
|
cy.visit(COMMUNITYSTATISTICSPAGE);
|
||||||
|
|
||||||
// <ds-community-statistics-page> tag must be loaded
|
// <ds-community-statistics-page> tag must be loaded
|
||||||
cy.get('ds-community-statistics-page').should('be.visible');
|
cy.get('ds-community-statistics-page').should('be.visible');
|
||||||
|
|
||||||
// Verify / wait until "Total Visits" table's label is non-empty
|
// Verify / wait until "Total Visits" table's label is non-empty
|
||||||
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
||||||
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
||||||
|
|
||||||
// Analyze <ds-community-statistics-page> for accessibility issues
|
// Analyze <ds-community-statistics-page> for accessibility issues
|
||||||
testA11y('ds-community-statistics-page');
|
testA11y('ds-community-statistics-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,13 +1,13 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Footer', () => {
|
describe('Footer', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
// Footer must first be visible
|
// Footer must first be visible
|
||||||
cy.get('ds-footer').should('be.visible');
|
cy.get('ds-footer').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-footer> for accessibility
|
// Analyze <ds-footer> for accessibility
|
||||||
testA11y('ds-footer');
|
testA11y('ds-footer');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,13 +1,38 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Header', () => {
|
describe('Header', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
// Header must first be visible
|
// Header must first be visible
|
||||||
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('ds-header');
|
testA11y('ds-header');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow for changing language to German (for example)', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
// Click the language switcher (globe) in header
|
||||||
|
cy.get('a[data-test="lang-switch"]').click();
|
||||||
|
// Click on the "Deusch" language in dropdown
|
||||||
|
cy.get('#language-menu-list li').contains('Deutsch').click();
|
||||||
|
|
||||||
|
// HTML "lang" attribute should switch to "de"
|
||||||
|
cy.get('html').invoke('attr', 'lang').should('eq', 'de');
|
||||||
|
|
||||||
|
// Login menu should now be in German
|
||||||
|
cy.get('a[data-test="login-menu"]').contains('Anmelden');
|
||||||
|
|
||||||
|
// Change back to English from language switcher
|
||||||
|
cy.get('a[data-test="lang-switch"]').click();
|
||||||
|
cy.get('#language-menu-list li').contains('English').click();
|
||||||
|
|
||||||
|
// HTML "lang" attribute should switch to "en"
|
||||||
|
cy.get('html').invoke('attr', 'lang').should('eq', 'en');
|
||||||
|
|
||||||
|
// Login menu should now be in English
|
||||||
|
cy.get('a[data-test="login-menu"]').contains('Log In');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
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,31 +1,32 @@
|
|||||||
import { REGEX_MATCH_NON_EMPTY_TEXT } 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[data-test="link-menu-item.menu.section.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(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item');
|
cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item');
|
||||||
cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item');
|
cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item');
|
||||||
|
|
||||||
cy.visit('/statistics');
|
cy.visit('/statistics');
|
||||||
|
|
||||||
// <ds-site-statistics-page> tag must be visable
|
// <ds-site-statistics-page> tag must be visible
|
||||||
cy.get('ds-site-statistics-page').should('be.visible');
|
cy.get('ds-site-statistics-page').should('be.visible');
|
||||||
|
|
||||||
// Verify / wait until "Total Visits" table's *last* label is non-empty
|
// Verify / wait until "Total Visits" table's *last* label is non-empty
|
||||||
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
||||||
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').last().contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').last().contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
||||||
// Wait an extra 500ms, just so all entries in Total Visits have loaded.
|
// Wait an extra 500ms, just so all entries in Total Visits have loaded.
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
|
|
||||||
// Analyze <ds-site-statistics-page> for accessibility issues
|
// Analyze <ds-site-statistics-page> for accessibility issues
|
||||||
testA11y('ds-site-statistics-page');
|
testA11y('ds-site-statistics-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,135 +1,180 @@
|
|||||||
import { Options } from 'cypress-axe';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
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');
|
const ITEM_EDIT_PAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')).concat('/edit');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// All tests start with visiting the Edit Item Page
|
// All tests start with visiting the Edit Item Page
|
||||||
cy.visit(ITEM_EDIT_PAGE);
|
cy.visit(ITEM_EDIT_PAGE);
|
||||||
|
|
||||||
// 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(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Item > Edit Metadata tab', () => {
|
describe('Edit Item > Edit Metadata tab', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="metadata"]').click();
|
cy.get('a[data-test="metadata"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="metadata"]').click();
|
||||||
|
|
||||||
// <ds-edit-item-page> tag must be loaded
|
// Our selected tab should be both visible & active
|
||||||
cy.get('ds-edit-item-page').should('be.visible');
|
cy.get('a[data-test="metadata"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="metadata"]').should('have.class', 'active');
|
||||||
|
|
||||||
// Analyze <ds-edit-item-page> for accessibility issues
|
// <ds-edit-item-page> tag must be loaded
|
||||||
testA11y('ds-edit-item-page');
|
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', () => {
|
describe('Edit Item > Status tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="status"]').click();
|
cy.get('a[data-test="status"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="status"]').click();
|
||||||
|
|
||||||
// <ds-item-status> tag must be loaded
|
// Our selected tab should be both visible & active
|
||||||
cy.get('ds-item-status').should('be.visible');
|
cy.get('a[data-test="status"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="status"]').should('have.class', 'active');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// <ds-item-status> tag must be loaded
|
||||||
testA11y('ds-item-status');
|
cy.get('ds-item-status').should('be.visible');
|
||||||
});
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-item-status');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Item > Bitstreams tab', () => {
|
describe('Edit Item > Bitstreams tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="bitstreams"]').click();
|
cy.get('a[data-test="bitstreams"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="bitstreams"]').click();
|
||||||
|
|
||||||
// <ds-item-bitstreams> tag must be loaded
|
// Our selected tab should be both visible & active
|
||||||
cy.get('ds-item-bitstreams').should('be.visible');
|
cy.get('a[data-test="bitstreams"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="bitstreams"]').should('have.class', 'active');
|
||||||
|
|
||||||
// Table of item bitstreams must also be loaded
|
// <ds-item-bitstreams> tag must be loaded
|
||||||
cy.get('div.item-bitstreams').should('be.visible');
|
cy.get('ds-item-bitstreams').should('be.visible');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// Table of item bitstreams must also be loaded
|
||||||
testA11y('ds-item-bitstreams',
|
cy.get('div.item-bitstreams').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-item-bitstreams',
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
// Currently Bitstreams page loads a pagination component per Bundle
|
// Currently Bitstreams page loads a pagination component per Bundle
|
||||||
// and they all use the same 'id="p-dad"'.
|
// and they all use the same 'id="p-dad"'.
|
||||||
'duplicate-id': { enabled: false },
|
'duplicate-id': { enabled: false },
|
||||||
}
|
},
|
||||||
} as Options
|
} as Options,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Item > Curate tab', () => {
|
describe('Edit Item > Curate tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="curate"]').click();
|
cy.get('a[data-test="curate"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="curate"]').click();
|
||||||
|
|
||||||
// <ds-item-curate> tag must be loaded
|
// Our selected tab should be both visible & active
|
||||||
cy.get('ds-item-curate').should('be.visible');
|
cy.get('a[data-test="curate"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="curate"]').should('have.class', 'active');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// <ds-item-curate> tag must be loaded
|
||||||
testA11y('ds-item-curate');
|
cy.get('ds-item-curate').should('be.visible');
|
||||||
});
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-item-curate');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Item > Relationships tab', () => {
|
describe('Edit Item > Relationships tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="relationships"]').click();
|
cy.get('a[data-test="relationships"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="relationships"]').click();
|
||||||
|
|
||||||
// <ds-item-relationships> tag must be loaded
|
// Our selected tab should be both visible & active
|
||||||
cy.get('ds-item-relationships').should('be.visible');
|
cy.get('a[data-test="relationships"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="relationships"]').should('have.class', 'active');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// <ds-item-relationships> tag must be loaded
|
||||||
testA11y('ds-item-relationships');
|
cy.get('ds-item-relationships').should('be.visible');
|
||||||
});
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-item-relationships');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Item > Version History tab', () => {
|
describe('Edit Item > Version History tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="versionhistory"]').click();
|
cy.get('a[data-test="versionhistory"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="versionhistory"]').click();
|
||||||
|
|
||||||
// <ds-item-version-history> tag must be loaded
|
// Our selected tab should be both visible & active
|
||||||
cy.get('ds-item-version-history').should('be.visible');
|
cy.get('a[data-test="versionhistory"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="versionhistory"]').should('have.class', 'active');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// <ds-item-version-history> tag must be loaded
|
||||||
testA11y('ds-item-version-history');
|
cy.get('ds-item-version-history').should('be.visible');
|
||||||
});
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-item-version-history');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Item > Access Control tab', () => {
|
describe('Edit Item > Access Control tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="access-control"]').click();
|
cy.get('a[data-test="access-control"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="access-control"]').click();
|
||||||
|
|
||||||
// <ds-item-access-control> tag must be loaded
|
// Our selected tab should be both visible & active
|
||||||
cy.get('ds-item-access-control').should('be.visible');
|
cy.get('a[data-test="access-control"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="access-control"]').should('have.class', 'active');
|
||||||
|
|
||||||
// Analyze for accessibility issues
|
// <ds-item-access-control> tag must be loaded
|
||||||
testA11y('ds-item-access-control');
|
cy.get('ds-item-access-control').should('be.visible');
|
||||||
});
|
|
||||||
|
// Analyze for accessibility issues
|
||||||
|
testA11y('ds-item-access-control');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Edit Item > Collection Mapper tab', () => {
|
describe('Edit Item > Collection Mapper tab', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.get('a[data-test="mapper"]').click();
|
cy.get('a[data-test="mapper"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="mapper"]').click();
|
||||||
|
|
||||||
// <ds-item-collection-mapper> tag must be loaded
|
// Our selected tab should be both visible & active
|
||||||
cy.get('ds-item-collection-mapper').should('be.visible');
|
cy.get('a[data-test="mapper"]').should('be.visible');
|
||||||
|
cy.get('a[data-test="mapper"]').should('have.class', 'active');
|
||||||
|
|
||||||
// Analyze entire page for accessibility issues
|
// <ds-item-collection-mapper> tag must be loaded
|
||||||
testA11y('ds-item-collection-mapper');
|
cy.get('ds-item-collection-mapper').should('be.visible');
|
||||||
|
|
||||||
// Click on the "Map new collections" tab
|
// Analyze entire page for accessibility issues
|
||||||
cy.get('li[data-test="mapTab"] a').click();
|
testA11y('ds-item-collection-mapper');
|
||||||
|
|
||||||
// Make sure search form is now visible
|
// Click on the "Map new collections" tab
|
||||||
cy.get('ds-search-form').should('be.visible');
|
cy.get('li[data-test="mapTab"] a').click();
|
||||||
|
|
||||||
// Analyze entire page (again) for accessibility issues
|
// Make sure search form is now visible
|
||||||
testA11y('ds-item-collection-mapper');
|
cy.get('ds-search-form').should('be.visible');
|
||||||
});
|
|
||||||
|
// Analyze entire page (again) for accessibility issues
|
||||||
|
testA11y('ds-item-collection-mapper');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,32 +1,32 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Item Page', () => {
|
describe('Item Page', () => {
|
||||||
const ITEMPAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
|
const ITEMPAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
|
||||||
const ENTITYPAGE = '/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
|
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', () => {
|
||||||
cy.visit(ITEMPAGE);
|
cy.visit(ITEMPAGE);
|
||||||
cy.location('pathname').should('eq', ENTITYPAGE);
|
cy.location('pathname').should('eq', ENTITYPAGE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit(ENTITYPAGE);
|
cy.visit(ENTITYPAGE);
|
||||||
|
|
||||||
// <ds-item-page> tag must be loaded
|
// <ds-item-page> tag must be loaded
|
||||||
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
|
||||||
testA11y('ds-item-page');
|
testA11y('ds-item-page');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass accessibility tests on full item page', () => {
|
it('should pass accessibility tests on full item page', () => {
|
||||||
cy.visit(ENTITYPAGE + '/full');
|
cy.visit(ENTITYPAGE + '/full');
|
||||||
|
|
||||||
// <ds-full-item-page> tag must be loaded
|
// <ds-full-item-page> tag must be loaded
|
||||||
cy.get('ds-full-item-page').should('be.visible');
|
cy.get('ds-full-item-page').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-full-item-page> for accessibility issues
|
// Analyze <ds-full-item-page> for accessibility issues
|
||||||
testA11y('ds-full-item-page');
|
testA11y('ds-full-item-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,42 +2,42 @@ 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(Cypress.env('DSPACE_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(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
|
cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
|
||||||
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
cy.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);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => {
|
it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => {
|
||||||
cy.visit(ITEMSTATISTICSPAGE);
|
cy.visit(ITEMSTATISTICSPAGE);
|
||||||
cy.get('ds-item-statistics-page').should('be.visible');
|
cy.get('ds-item-statistics-page').should('be.visible');
|
||||||
cy.get('ds-item-page').should('not.exist');
|
cy.get('ds-item-page').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a "Total visits" section', () => {
|
it('should contain a "Total visits" section', () => {
|
||||||
cy.visit(ITEMSTATISTICSPAGE);
|
cy.visit(ITEMSTATISTICSPAGE);
|
||||||
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
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(Cypress.env('DSPACE_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', () => {
|
||||||
cy.visit(ITEMSTATISTICSPAGE);
|
cy.visit(ITEMSTATISTICSPAGE);
|
||||||
|
|
||||||
// <ds-item-statistics-page> tag must be loaded
|
// <ds-item-statistics-page> tag must be loaded
|
||||||
cy.get('ds-item-statistics-page').should('be.visible');
|
cy.get('ds-item-statistics-page').should('be.visible');
|
||||||
|
|
||||||
// Verify / wait until "Total Visits" table's label is non-empty
|
// Verify / wait until "Total Visits" table's label is non-empty
|
||||||
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
||||||
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
||||||
|
|
||||||
// Analyze <ds-item-statistics-page> for accessibility issues
|
// Analyze <ds-item-statistics-page> for accessibility issues
|
||||||
testA11y('ds-item-statistics-page');
|
testA11y('ds-item-statistics-page');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
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,150 +1,150 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
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-header [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-header [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-header [data-test="email"]').type(email);
|
cy.get('[data-test="email"]').type(email);
|
||||||
// Enter password
|
// Enter password
|
||||||
cy.get('ds-themed-header [data-test="password"]').type(password);
|
cy.get('[data-test="password"]').type(password);
|
||||||
// Click login button
|
// Click login button
|
||||||
cy.get('ds-themed-header [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-header [data-test="email"]').type(email);
|
cy.get('[data-test="email"]').type(email);
|
||||||
cy.get('ds-themed-header [data-test="password"]').type(password);
|
cy.get('[data-test="password"]').type(password);
|
||||||
cy.get('ds-themed-header [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-header [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(Cypress.env('DSPACE_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
|
||||||
cy.get('ds-log-in').should('exist');
|
cy.get('ds-log-in').should('exist');
|
||||||
|
|
||||||
// Login, and the <ds-log-in> tag should no longer exist
|
// Login, and the <ds-log-in> tag should no longer exist
|
||||||
page.openLoginMenu();
|
page.openLoginMenu();
|
||||||
cy.get('.form-login').should('be.visible');
|
cy.get('.form-login').should('be.visible');
|
||||||
|
|
||||||
page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_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
|
||||||
cy.url().should('include', ENTITYPAGE);
|
cy.url().should('include', ENTITYPAGE);
|
||||||
|
|
||||||
// Open user menu, verify user menu & logout button now available
|
// Open user menu, verify user menu & logout button now available
|
||||||
page.openUserMenu();
|
page.openUserMenu();
|
||||||
cy.get('ds-user-menu').should('be.visible');
|
cy.get('ds-user-menu').should('be.visible');
|
||||||
cy.get('ds-log-out').should('be.visible');
|
cy.get('ds-log-out').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should login when clicking enter key & stay on same page', () => {
|
it('should login when clicking enter key & stay on same page', () => {
|
||||||
cy.visit('/home');
|
cy.visit('/home');
|
||||||
|
|
||||||
// Open login menu in header & verify <ds-log-in> tag is visible
|
// Open login menu in header & verify <ds-log-in> tag is visible
|
||||||
page.openLoginMenu();
|
page.openLoginMenu();
|
||||||
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(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_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');
|
||||||
|
|
||||||
// Open user menu, verify user menu & logout button now available
|
// Open user menu, verify user menu & logout button now available
|
||||||
page.openUserMenu();
|
page.openUserMenu();
|
||||||
cy.get('ds-user-menu').should('be.visible');
|
cy.get('ds-user-menu').should('be.visible');
|
||||||
cy.get('ds-log-out').should('be.visible');
|
cy.get('ds-log-out').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support logout', () => {
|
it('should support logout', () => {
|
||||||
// First authenticate & access homepage
|
// First authenticate & access homepage
|
||||||
cy.login(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_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
|
||||||
cy.get('ds-log-in').should('not.exist');
|
cy.get('ds-log-in').should('not.exist');
|
||||||
cy.get('ds-log-out').should('exist');
|
cy.get('ds-log-out').should('exist');
|
||||||
|
|
||||||
// Click logout button
|
// Click logout button
|
||||||
page.openUserMenu();
|
page.openUserMenu();
|
||||||
page.submitLogoutByPressingButton();
|
page.submitLogoutByPressingButton();
|
||||||
|
|
||||||
// Verify ds-log-in tag now exists
|
// Verify ds-log-in tag now exists
|
||||||
cy.get('ds-log-in').should('exist');
|
cy.get('ds-log-in').should('exist');
|
||||||
cy.get('ds-log-out').should('not.exist');
|
cy.get('ds-log-out').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow new user registration', () => {
|
it('should allow new user registration', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
page.openLoginMenu();
|
page.openLoginMenu();
|
||||||
|
|
||||||
// Registration link should be visible
|
// Registration link should be visible
|
||||||
cy.get('ds-themed-header [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-header [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
|
// Test accessibility of this page
|
||||||
testA11y('ds-register-email');
|
testA11y('ds-register-email');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow forgot password', () => {
|
it('should allow forgot password', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
page.openLoginMenu();
|
page.openLoginMenu();
|
||||||
|
|
||||||
// Forgot password link should be visible
|
// Forgot password link should be visible
|
||||||
cy.get('ds-themed-header [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-header [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
|
// Test accessibility of this page
|
||||||
testA11y('ds-forgot-email');
|
testA11y('ds-forgot-email');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass accessibility tests in menus', () => {
|
it('should pass accessibility tests in menus', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
// Open login menu & verify accessibility
|
// Open login menu & verify accessibility
|
||||||
page.openLoginMenu();
|
page.openLoginMenu();
|
||||||
cy.get('ds-log-in').should('exist');
|
cy.get('ds-log-in').should('exist');
|
||||||
testA11y('ds-log-in');
|
testA11y('ds-log-in');
|
||||||
|
|
||||||
// Now login
|
// Now login
|
||||||
page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_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');
|
||||||
|
|
||||||
// Open user menu, verify user menu accesibility
|
// Open user menu, verify user menu accessibility
|
||||||
page.openUserMenu();
|
page.openUserMenu();
|
||||||
cy.get('ds-user-menu').should('be.visible');
|
cy.get('ds-user-menu').should('be.visible');
|
||||||
testA11y('ds-user-menu');
|
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,134 +1,134 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('My DSpace page', () => {
|
describe('My DSpace page', () => {
|
||||||
it('should display recent submissions and pass accessibility tests', () => {
|
it('should display recent submissions and pass 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(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_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');
|
||||||
|
|
||||||
// At least one recent submission should be displayed
|
// At least one recent submission should be displayed
|
||||||
cy.get('[data-test="list-object"]').should('be.visible');
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
|
|
||||||
// Click each filter toggle to open *every* filter
|
// Click each filter toggle to open *every* filter
|
||||||
// (As we want to scan filter section for accessibility issues as well)
|
// (As we want to scan filter section for accessibility issues as well)
|
||||||
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('ds-my-dspace-page');
|
testA11y('ds-my-dspace-page');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a working detailed view that passes accessibility tests', () => {
|
||||||
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
||||||
|
|
||||||
|
cy.get('ds-my-dspace-page').should('be.visible');
|
||||||
|
|
||||||
|
// Click button in sidebar to display detailed view
|
||||||
|
cy.get('ds-search-sidebar [data-test="detail-view"]').click();
|
||||||
|
|
||||||
|
cy.get('ds-object-detail').should('be.visible');
|
||||||
|
|
||||||
|
// Analyze <ds-my-dspace-page> for accessibility issues
|
||||||
|
testA11y('ds-my-dspace-page');
|
||||||
|
});
|
||||||
|
|
||||||
|
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
|
||||||
|
it('should let you start a new submission & edit in-progress submissions', () => {
|
||||||
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
||||||
|
|
||||||
|
// Open the New Submission dropdown
|
||||||
|
cy.get('button[data-test="submission-dropdown"]').click();
|
||||||
|
// Click on the "Item" type in that dropdown
|
||||||
|
cy.get('#entityControlsDropdownMenu button[title="none"]').click();
|
||||||
|
|
||||||
|
// 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_COLLECTION_NAME'));
|
||||||
|
|
||||||
|
// Click on the button matching that known Collection name
|
||||||
|
cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')).concat('"]')).click();
|
||||||
|
|
||||||
|
// 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_COLLECTION_NAME'));
|
||||||
|
|
||||||
|
// Now that we've created a submission, we'll test that we can go back and Edit it.
|
||||||
|
// Get our Submission URL, to parse out the ID of this new submission
|
||||||
|
cy.location().then(fullUrl => {
|
||||||
|
// This will be the full path (/workspaceitems/[id]/edit)
|
||||||
|
const path = fullUrl.pathname;
|
||||||
|
// Split on the slashes
|
||||||
|
const subpaths = path.split('/');
|
||||||
|
// Part 2 will be the [id] of the submission
|
||||||
|
const id = subpaths[2];
|
||||||
|
|
||||||
|
// Click the "Save for Later" button to save this submission
|
||||||
|
cy.get('ds-submission-form-footer [data-test="save-for-later"]').click();
|
||||||
|
|
||||||
|
// "Save for Later" should send us to MyDSpace
|
||||||
|
cy.url().should('include', '/mydspace');
|
||||||
|
|
||||||
|
// Close any open notifications, to make sure they don't get in the way of next steps
|
||||||
|
cy.get('[data-dismiss="alert"]').click({ multiple: true });
|
||||||
|
|
||||||
|
// This is the GET command that will actually run the search
|
||||||
|
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
||||||
|
// On MyDSpace, find the submission we just created via its ID
|
||||||
|
cy.get('[data-test="search-box"]').type(id);
|
||||||
|
cy.get('[data-test="search-button"]').click();
|
||||||
|
|
||||||
|
// Wait for search results to come back from the above GET command
|
||||||
|
cy.wait('@search-results');
|
||||||
|
|
||||||
|
// Click the Edit button for this in-progress submission
|
||||||
|
cy.get('#edit_' + id).click();
|
||||||
|
|
||||||
|
// Should send us back to the submission form
|
||||||
|
cy.url().should('include', '/workspaceitems/' + id + '/edit');
|
||||||
|
|
||||||
|
// Discard our new submission by clicking Discard in Submission form & confirming
|
||||||
|
cy.get('ds-submission-form-footer [data-test="discard"]').click();
|
||||||
|
cy.get('button#discard_submit').click();
|
||||||
|
|
||||||
|
// Discarding should send us back to MyDSpace
|
||||||
|
cy.url().should('include', '/mydspace');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should have a working detailed view that passes accessibility tests', () => {
|
it('should let you import from external sources', () => {
|
||||||
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(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_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');
|
// Open the New Import dropdown
|
||||||
|
cy.get('button[data-test="import-dropdown"]').click();
|
||||||
|
// Click on the "Item" type in that dropdown
|
||||||
|
cy.get('#importControlsDropdownMenu button[title="none"]').click();
|
||||||
|
|
||||||
// Click button in sidebar to display detailed view
|
// New URL should include /import-external, as we've moved to the import page
|
||||||
cy.get('ds-search-sidebar [data-test="detail-view"]').click();
|
cy.url().should('include', '/import-external');
|
||||||
|
|
||||||
cy.get('ds-object-detail').should('be.visible');
|
// The external import searchbox should be visible
|
||||||
|
cy.get('ds-submission-import-external-searchbar').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-my-dspace-page> for accessibility issues
|
// Test for accessibility issues
|
||||||
testA11y('ds-my-dspace-page');
|
testA11y('ds-submission-import-external');
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
|
|
||||||
it('should let you start a new submission & edit in-progress submissions', () => {
|
|
||||||
cy.visit('/mydspace');
|
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
|
||||||
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
|
||||||
|
|
||||||
// Open the New Submission dropdown
|
|
||||||
cy.get('button[data-test="submission-dropdown"]').click();
|
|
||||||
// Click on the "Item" type in that dropdown
|
|
||||||
cy.get('#entityControlsDropdownMenu button[title="none"]').click();
|
|
||||||
|
|
||||||
// This should display the <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_COLLECTION_NAME'));
|
|
||||||
|
|
||||||
// Click on the button matching that known Collection name
|
|
||||||
cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')).concat('"]')).click();
|
|
||||||
|
|
||||||
// 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_COLLECTION_NAME'));
|
|
||||||
|
|
||||||
// Now that we've created a submission, we'll test that we can go back and Edit it.
|
|
||||||
// Get our Submission URL, to parse out the ID of this new submission
|
|
||||||
cy.location().then(fullUrl => {
|
|
||||||
// This will be the full path (/workspaceitems/[id]/edit)
|
|
||||||
const path = fullUrl.pathname;
|
|
||||||
// Split on the slashes
|
|
||||||
const subpaths = path.split('/');
|
|
||||||
// Part 2 will be the [id] of the submission
|
|
||||||
const id = subpaths[2];
|
|
||||||
|
|
||||||
// Click the "Save for Later" button to save this submission
|
|
||||||
cy.get('ds-submission-form-footer [data-test="save-for-later"]').click();
|
|
||||||
|
|
||||||
// "Save for Later" should send us to MyDSpace
|
|
||||||
cy.url().should('include', '/mydspace');
|
|
||||||
|
|
||||||
// Close any open notifications, to make sure they don't get in the way of next steps
|
|
||||||
cy.get('[data-dismiss="alert"]').click({multiple: true});
|
|
||||||
|
|
||||||
// This is the GET command that will actually run the search
|
|
||||||
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
|
||||||
// On MyDSpace, find the submission we just created via its ID
|
|
||||||
cy.get('[data-test="search-box"]').type(id);
|
|
||||||
cy.get('[data-test="search-button"]').click();
|
|
||||||
|
|
||||||
// Wait for search results to come back from the above GET command
|
|
||||||
cy.wait('@search-results');
|
|
||||||
|
|
||||||
// Click the Edit button for this in-progress submission
|
|
||||||
cy.get('#edit_' + id).click();
|
|
||||||
|
|
||||||
// Should send us back to the submission form
|
|
||||||
cy.url().should('include', '/workspaceitems/' + id + '/edit');
|
|
||||||
|
|
||||||
// Discard our new submission by clicking Discard in Submission form & confirming
|
|
||||||
cy.get('ds-submission-form-footer [data-test="discard"]').click();
|
|
||||||
cy.get('button#discard_submit').click();
|
|
||||||
|
|
||||||
// Discarding should send us back to MyDSpace
|
|
||||||
cy.url().should('include', '/mydspace');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should let you import from external sources', () => {
|
|
||||||
cy.visit('/mydspace');
|
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
|
||||||
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
|
||||||
|
|
||||||
// Open the New Import dropdown
|
|
||||||
cy.get('button[data-test="import-dropdown"]').click();
|
|
||||||
// Click on the "Item" type in that dropdown
|
|
||||||
cy.get('#importControlsDropdownMenu button[title="none"]').click();
|
|
||||||
|
|
||||||
// New URL should include /import-external, as we've moved to the import page
|
|
||||||
cy.url().should('include', '/import-external');
|
|
||||||
|
|
||||||
// The external import searchbox should be visible
|
|
||||||
cy.get('ds-submission-import-external-searchbar').should('be.visible');
|
|
||||||
|
|
||||||
// Test for accessibility issues
|
|
||||||
testA11y('ds-submission-import-external');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
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,18 +1,18 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
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 does not 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
|
// Analyze <ds-pagenotfound> for accessibility issues
|
||||||
testA11y('ds-pagenotfound');
|
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', () => {
|
||||||
cy.visit('/home');
|
cy.visit('/home');
|
||||||
cy.get('ds-pagenotfound').should('not.exist');
|
cy.get('ds-pagenotfound').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
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,64 +1,64 @@
|
|||||||
const page = {
|
const page = {
|
||||||
fillOutQueryInNavBar(query) {
|
fillOutQueryInNavBar(query) {
|
||||||
// Click the magnifying glass
|
// Click the magnifying glass
|
||||||
cy.get('ds-themed-header [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-header [data-test="header-search-box"]').type(query);
|
cy.get('ds-header [data-test="header-search-box"]').type(query);
|
||||||
},
|
},
|
||||||
submitQueryByPressingEnter() {
|
submitQueryByPressingEnter() {
|
||||||
cy.get('ds-themed-header [data-test="header-search-box"]').type('{enter}');
|
cy.get('ds-header [data-test="header-search-box"]').type('{enter}');
|
||||||
},
|
},
|
||||||
submitQueryByPressingIcon() {
|
submitQueryByPressingIcon() {
|
||||||
cy.get('ds-themed-header [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 = Cypress.env('DSPACE_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('/');
|
||||||
// This is the GET command that will actually run the search
|
// This is the GET command that will actually run the search
|
||||||
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
||||||
// Run the search
|
// Run the search
|
||||||
page.fillOutQueryInNavBar(query);
|
page.fillOutQueryInNavBar(query);
|
||||||
page.submitQueryByPressingEnter();
|
page.submitQueryByPressingEnter();
|
||||||
// New URL should include query param
|
// New URL should include query param
|
||||||
cy.url().should('include', 'query='.concat(query));
|
cy.url().should('include', 'query='.concat(query));
|
||||||
// Wait for search results to come back from the above GET command
|
// Wait for search results to come back from the above GET command
|
||||||
cy.wait('@search-results');
|
cy.wait('@search-results');
|
||||||
// At least one search result should be displayed
|
// At least one search result should be displayed
|
||||||
cy.get('[data-test="list-object"]').should('be.visible');
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should go to search page with correct query if submitted (from search)', () => {
|
it('should go to search page with correct query if submitted (from search)', () => {
|
||||||
cy.visit('/search');
|
cy.visit('/search');
|
||||||
// This is the GET command that will actually run the search
|
// This is the GET command that will actually run the search
|
||||||
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
||||||
// Run the search
|
// Run the search
|
||||||
page.fillOutQueryInNavBar(query);
|
page.fillOutQueryInNavBar(query);
|
||||||
page.submitQueryByPressingEnter();
|
page.submitQueryByPressingEnter();
|
||||||
// New URL should include query param
|
// New URL should include query param
|
||||||
cy.url().should('include', 'query='.concat(query));
|
cy.url().should('include', 'query='.concat(query));
|
||||||
// Wait for search results to come back from the above GET command
|
// Wait for search results to come back from the above GET command
|
||||||
cy.wait('@search-results');
|
cy.wait('@search-results');
|
||||||
// At least one search result should be displayed
|
// At least one search result should be displayed
|
||||||
cy.get('[data-test="list-object"]').should('be.visible');
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow user to also submit query by clicking icon', () => {
|
it('should allow user to also submit query by clicking icon', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
// This is the GET command that will actually run the search
|
// This is the GET command that will actually run the search
|
||||||
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
||||||
// Run the search
|
// Run the search
|
||||||
page.fillOutQueryInNavBar(query);
|
page.fillOutQueryInNavBar(query);
|
||||||
page.submitQueryByPressingIcon();
|
page.submitQueryByPressingIcon();
|
||||||
// New URL should include query param
|
// New URL should include query param
|
||||||
cy.url().should('include', 'query='.concat(query));
|
cy.url().should('include', 'query='.concat(query));
|
||||||
// Wait for search results to come back from the above GET command
|
// Wait for search results to come back from the above GET command
|
||||||
cy.wait('@search-results');
|
cy.wait('@search-results');
|
||||||
// At least one search result should be displayed
|
// At least one search result should be displayed
|
||||||
cy.get('[data-test="list-object"]').should('be.visible');
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,57 +1,57 @@
|
|||||||
import { Options } from 'cypress-axe';
|
|
||||||
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!
|
// NOTE: these tests currently assume this query will return results!
|
||||||
const query = Cypress.env('DSPACE_TEST_SEARCH_TERM');
|
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');
|
||||||
// Type query in searchbox & click search button
|
// Type query in searchbox & click search button
|
||||||
cy.get('[data-test="search-box"]').type(queryString);
|
cy.get('[data-test="search-box"]').type(queryString);
|
||||||
cy.get('[data-test="search-button"]').click();
|
cy.get('[data-test="search-button"]').click();
|
||||||
cy.url().should('include', 'query=' + encodeURI(queryString));
|
cy.url().should('include', 'query=' + encodeURI(queryString));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load results and pass accessibility tests', () => {
|
it('should load results and pass accessibility tests', () => {
|
||||||
cy.visit('/search?query='.concat(query));
|
cy.visit('/search?query='.concat(query));
|
||||||
cy.get('[data-test="search-box"]').should('have.value', query);
|
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');
|
||||||
|
|
||||||
// At least one search result should be displayed
|
// At least one search result should be displayed
|
||||||
cy.get('[data-test="list-object"]').should('be.visible');
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
|
|
||||||
// Click each filter toggle to open *every* filter
|
// Click each filter toggle to open *every* filter
|
||||||
// (As we want to scan filter section for accessibility issues as well)
|
// (As we want to scan filter section for accessibility issues as well)
|
||||||
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('ds-search-page');
|
testA11y('ds-search-page');
|
||||||
});
|
});
|
||||||
|
|
||||||
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(query));
|
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();
|
||||||
|
|
||||||
// <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');
|
||||||
|
|
||||||
// At least one grid object (card) should be displayed
|
// At least one grid object (card) should be displayed
|
||||||
cy.get('[data-test="grid-object"]').should('be.visible');
|
cy.get('[data-test="grid-object"]').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-search-page> for accessibility issues
|
// Analyze <ds-search-page> for accessibility issues
|
||||||
testA11y('ds-search-page',
|
testA11y('ds-search-page',
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
// Card titles fail this test currently
|
// Card titles fail this test currently
|
||||||
'heading-order': { enabled: false }
|
'heading-order': { enabled: false },
|
||||||
}
|
},
|
||||||
} as Options
|
} as Options,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -4,224 +4,224 @@ import { Options } from 'cypress-axe';
|
|||||||
|
|
||||||
describe('New Submission page', () => {
|
describe('New Submission page', () => {
|
||||||
|
|
||||||
// NOTE: We already test that new Item 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(Cypress.env('DSPACE_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(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_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');
|
||||||
|
|
||||||
// The Submission edit form tag should be visible
|
// The Submission edit form tag should be visible
|
||||||
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', Cypress.env('DSPACE_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');
|
||||||
cy.get('div#section_traditionalpagetwo').should('be.visible');
|
cy.get('div#section_traditionalpagetwo').should('be.visible');
|
||||||
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
|
// Test entire page for accessibility
|
||||||
testA11y('ds-submission-edit',
|
testA11y('ds-submission-edit',
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
// Author & Subject fields have invalid "aria-multiline" attrs.
|
// Author & Subject fields have invalid "aria-multiline" attrs.
|
||||||
// See https://github.com/DSpace/dspace-angular/issues/1272
|
// See https://github.com/DSpace/dspace-angular/issues/1272
|
||||||
'aria-allowed-attr': { enabled: false },
|
'aria-allowed-attr': { enabled: false },
|
||||||
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
|
// 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
|
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
||||||
'aria-required-children': { enabled: false },
|
'aria-required-children': { enabled: false },
|
||||||
'nested-interactive': { enabled: false },
|
'nested-interactive': { enabled: false },
|
||||||
// All select boxes fail to have a name / aria-label.
|
// 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
|
// This is a bug in ng-dynamic-forms and may require https://github.com/DSpace/dspace-angular/issues/2216
|
||||||
'select-name': { enabled: false },
|
'select-name': { enabled: false },
|
||||||
}
|
},
|
||||||
|
|
||||||
} as Options
|
} 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();
|
||||||
cy.get('button#discard_submit').click();
|
cy.get('button#discard_submit').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should block submission & show errors if required fields are missing', () => {
|
||||||
|
// Create a new submission
|
||||||
|
cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none'));
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
||||||
|
|
||||||
|
// Attempt an immediate deposit without filling out any fields
|
||||||
|
cy.get('button#deposit').click();
|
||||||
|
|
||||||
|
// A warning alert should display.
|
||||||
|
cy.get('ds-notification div.alert-success').should('not.exist');
|
||||||
|
cy.get('ds-notification div.alert-warning').should('be.visible');
|
||||||
|
|
||||||
|
// First section should have an exclamation error in the header
|
||||||
|
// (as it has required fields)
|
||||||
|
cy.get('div#traditionalpageone-header i.fa-exclamation-circle').should('be.visible');
|
||||||
|
|
||||||
|
// Title field should have class "is-invalid" applied, as it's required
|
||||||
|
cy.get('input#dc_title').should('have.class', 'is-invalid');
|
||||||
|
|
||||||
|
// Date Year field should also have "is-valid" class
|
||||||
|
cy.get('input#dc_date_issued_year').should('have.class', 'is-invalid');
|
||||||
|
|
||||||
|
// FINALLY, cleanup after ourselves. This also exercises the MyDSpace delete button.
|
||||||
|
// Get our Submission URL, to parse out the ID of this submission
|
||||||
|
cy.location().then(fullUrl => {
|
||||||
|
// This will be the full path (/workspaceitems/[id]/edit)
|
||||||
|
const path = fullUrl.pathname;
|
||||||
|
// Split on the slashes
|
||||||
|
const subpaths = path.split('/');
|
||||||
|
// Part 2 will be the [id] of the submission
|
||||||
|
const id = subpaths[2];
|
||||||
|
|
||||||
|
// Even though form is incomplete, the "Save for Later" button should still work
|
||||||
|
cy.get('button#saveForLater').click();
|
||||||
|
|
||||||
|
// "Save for Later" should send us to MyDSpace
|
||||||
|
cy.url().should('include', '/mydspace');
|
||||||
|
|
||||||
|
// A success alert should be visible
|
||||||
|
cy.get('ds-notification div.alert-success').should('be.visible');
|
||||||
|
// Now, dismiss any open alert boxes (may be multiple, as tests run quickly)
|
||||||
|
cy.get('[data-dismiss="alert"]').click({ multiple: true });
|
||||||
|
|
||||||
|
// This is the GET command that will actually run the search
|
||||||
|
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
||||||
|
// On MyDSpace, find the submission we just saved via its ID
|
||||||
|
cy.get('[data-test="search-box"]').type(id);
|
||||||
|
cy.get('[data-test="search-button"]').click();
|
||||||
|
|
||||||
|
// Wait for search results to come back from the above GET command
|
||||||
|
cy.wait('@search-results');
|
||||||
|
|
||||||
|
// Delete our created submission & confirm deletion
|
||||||
|
cy.get('button#delete_' + id).click();
|
||||||
|
cy.get('button#delete_confirm').click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
||||||
|
// Create a new submission
|
||||||
|
cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none'));
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
||||||
|
|
||||||
|
// Fill out all required fields (Title, Date)
|
||||||
|
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
|
||||||
|
cy.get('input#dc_date_issued_year').type('2022');
|
||||||
|
|
||||||
|
// Confirm the required license by checking checkbox
|
||||||
|
// (NOTE: requires "force:true" cause Cypress claims this checkbox is covered by its own <span>)
|
||||||
|
cy.get('input#granted').check( { force: true } );
|
||||||
|
|
||||||
|
// Before using Cypress drag & drop, we have to manually trigger the "dragover" event.
|
||||||
|
// This ensures our UI displays the dropzone that covers the entire submission page.
|
||||||
|
// (For some reason Cypress drag & drop doesn't trigger this even itself & upload won't work without this trigger)
|
||||||
|
cy.get('ds-uploader').trigger('dragover');
|
||||||
|
|
||||||
|
// This is the POST command that will upload the file
|
||||||
|
cy.intercept('POST', '/server/api/submission/workspaceitems/*').as('upload');
|
||||||
|
|
||||||
|
// Upload our DSpace logo via drag & drop onto submission form
|
||||||
|
// cy.get('div#section_upload')
|
||||||
|
cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.svg', {
|
||||||
|
action: 'drag-drop',
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should block submission & show errors if required fields are missing', () => {
|
// Wait for upload to complete before proceeding
|
||||||
// Create a new submission
|
cy.wait('@upload');
|
||||||
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.
|
// Wait for deposit button to not be disabled & click it.
|
||||||
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
cy.get('button#deposit').should('not.be.disabled').click();
|
||||||
|
|
||||||
// Attempt an immediate deposit without filling out any fields
|
// No warnings should exist. Instead, just successful deposit alert is displayed
|
||||||
cy.get('button#deposit').click();
|
cy.get('ds-notification div.alert-warning').should('not.exist');
|
||||||
|
cy.get('ds-notification div.alert-success').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
// A warning alert should display.
|
it('is possible to submit a new "Person" and that form passes accessibility', () => {
|
||||||
cy.get('ds-notification div.alert-success').should('not.exist');
|
// To submit a different entity type, we'll start from MyDSpace
|
||||||
cy.get('ds-notification div.alert-warning').should('be.visible');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
// First section should have an exclamation error in the header
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
// (as it has required fields)
|
// NOTE: At this time, we MUST login as admin to submit Person objects
|
||||||
cy.get('div#traditionalpageone-header i.fa-exclamation-circle').should('be.visible');
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
|
|
||||||
// Title field should have class "is-invalid" applied, as it's required
|
// Open the New Submission dropdown
|
||||||
cy.get('input#dc_title').should('have.class', 'is-invalid');
|
cy.get('button[data-test="submission-dropdown"]').click();
|
||||||
|
// Click on the "Person" type in that dropdown
|
||||||
|
cy.get('#entityControlsDropdownMenu button[title="Person"]').click();
|
||||||
|
|
||||||
// Date Year field should also have "is-valid" class
|
// This should display the <ds-create-item-parent-selector> (popup window)
|
||||||
cy.get('input#dc_date_issued_year').should('have.class', 'is-invalid');
|
cy.get('ds-create-item-parent-selector').should('be.visible');
|
||||||
|
|
||||||
// FINALLY, cleanup after ourselves. This also exercises the MyDSpace delete button.
|
// Type in a known Collection name in the search box
|
||||||
// Get our Submission URL, to parse out the ID of this submission
|
cy.get('ds-authorized-collection-selector input[type="search"]').type(Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME'));
|
||||||
cy.location().then(fullUrl => {
|
|
||||||
// This will be the full path (/workspaceitems/[id]/edit)
|
|
||||||
const path = fullUrl.pathname;
|
|
||||||
// Split on the slashes
|
|
||||||
const subpaths = path.split('/');
|
|
||||||
// Part 2 will be the [id] of the submission
|
|
||||||
const id = subpaths[2];
|
|
||||||
|
|
||||||
// Even though form is incomplete, the "Save for Later" button should still work
|
// Click on the button matching that known Collection name
|
||||||
cy.get('button#saveForLater').click();
|
cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME')).concat('"]')).click();
|
||||||
|
|
||||||
// "Save for Later" should send us to MyDSpace
|
// New URL should include /workspaceitems, as we've started a new submission
|
||||||
cy.url().should('include', '/mydspace');
|
cy.url().should('include', '/workspaceitems');
|
||||||
|
|
||||||
// A success alert should be visible
|
// The Submission edit form tag should be visible
|
||||||
cy.get('ds-notification div.alert-success').should('be.visible');
|
cy.get('ds-submission-edit').should('be.visible');
|
||||||
// Now, dismiss any open alert boxes (may be multiple, as tests run quickly)
|
|
||||||
cy.get('[data-dismiss="alert"]').click({multiple: true});
|
|
||||||
|
|
||||||
// This is the GET command that will actually run the search
|
// A Collection menu button should exist & its value should be the selected collection
|
||||||
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');
|
cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME'));
|
||||||
// On MyDSpace, find the submission we just saved via its ID
|
|
||||||
cy.get('[data-test="search-box"]').type(id);
|
|
||||||
cy.get('[data-test="search-button"]').click();
|
|
||||||
|
|
||||||
// Wait for search results to come back from the above GET command
|
// 3 sections should be visible by default
|
||||||
cy.wait('@search-results');
|
cy.get('div#section_personStep').should('be.visible');
|
||||||
|
cy.get('div#section_upload').should('be.visible');
|
||||||
|
cy.get('div#section_license').should('be.visible');
|
||||||
|
|
||||||
// Delete our created submission & confirm deletion
|
// Test entire page for accessibility
|
||||||
cy.get('button#delete_' + id).click();
|
testA11y('ds-submission-edit',
|
||||||
cy.get('button#delete_confirm').click();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
|
||||||
// Create a new submission
|
|
||||||
cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none'));
|
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
|
||||||
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
|
|
||||||
|
|
||||||
// Fill out all required fields (Title, Date)
|
|
||||||
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
|
|
||||||
cy.get('input#dc_date_issued_year').type('2022');
|
|
||||||
|
|
||||||
// Confirm the required license by checking checkbox
|
|
||||||
// (NOTE: requires "force:true" cause Cypress claims this checkbox is covered by its own <span>)
|
|
||||||
cy.get('input#granted').check( {force: true} );
|
|
||||||
|
|
||||||
// Before using Cypress drag & drop, we have to manually trigger the "dragover" event.
|
|
||||||
// This ensures our UI displays the dropzone that covers the entire submission page.
|
|
||||||
// (For some reason Cypress drag & drop doesn't trigger this even itself & upload won't work without this trigger)
|
|
||||||
cy.get('ds-uploader').trigger('dragover');
|
|
||||||
|
|
||||||
// This is the POST command that will upload the file
|
|
||||||
cy.intercept('POST', '/server/api/submission/workspaceitems/*').as('upload');
|
|
||||||
|
|
||||||
// Upload our DSpace logo via drag & drop onto submission form
|
|
||||||
// cy.get('div#section_upload')
|
|
||||||
cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.png', {
|
|
||||||
action: 'drag-drop'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for upload to complete before proceeding
|
|
||||||
cy.wait('@upload');
|
|
||||||
|
|
||||||
// Wait for deposit button to not be disabled & click it.
|
|
||||||
cy.get('button#deposit').should('not.be.disabled').click();
|
|
||||||
|
|
||||||
// No warnings should exist. Instead, just successful deposit alert is displayed
|
|
||||||
cy.get('ds-notification div.alert-warning').should('not.exist');
|
|
||||||
cy.get('ds-notification div.alert-success').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
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: {
|
rules: {
|
||||||
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
|
// 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
|
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
||||||
'aria-required-children': { enabled: false },
|
'aria-required-children': { enabled: false },
|
||||||
'nested-interactive': { enabled: false },
|
'nested-interactive': { enabled: false },
|
||||||
}
|
},
|
||||||
|
|
||||||
} as Options
|
} as Options,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Click the lookup button next to "Publication" field
|
// Click the lookup button next to "Publication" field
|
||||||
cy.get('button[data-test="lookup-button"]').click();
|
cy.get('button[data-test="lookup-button"]').click();
|
||||||
|
|
||||||
// A popup modal window should be visible
|
// A popup modal window should be visible
|
||||||
cy.get('ds-dynamic-lookup-relation-modal').should('be.visible');
|
cy.get('ds-dynamic-lookup-relation-modal').should('be.visible');
|
||||||
|
|
||||||
// Popup modal should also pass accessibility tests
|
// Popup modal should also pass accessibility tests
|
||||||
//testA11y('ds-dynamic-lookup-relation-modal');
|
//testA11y('ds-dynamic-lookup-relation-modal');
|
||||||
testA11y({
|
testA11y({
|
||||||
include: ['ds-dynamic-lookup-relation-modal'],
|
include: ['ds-dynamic-lookup-relation-modal'],
|
||||||
exclude: [
|
exclude: [
|
||||||
['ul.nav-tabs'] // Tabs at top of model have several issues which seem to be caused by ng-bootstrap
|
['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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
});
|
@@ -9,51 +9,51 @@ 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) => {
|
||||||
on('task', {
|
on('task', {
|
||||||
// Define "log" and "table" tasks, used for logging accessibility errors during CI
|
// Define "log" and "table" tasks, used for logging accessibility errors during CI
|
||||||
// Borrowed from https://github.com/component-driven/cypress-axe#in-cypress-plugins-file
|
// Borrowed from https://github.com/component-driven/cypress-axe#in-cypress-plugins-file
|
||||||
log(message: string) {
|
log(message: string) {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
table(message: string) {
|
table(message: string) {
|
||||||
console.table(message);
|
console.table(message);
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
// Cypress doesn't have access to the running application in Node.js.
|
// 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.
|
// 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 &
|
// 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.
|
// is regenerated at runtime each time the Angular UI application starts up.
|
||||||
readUIConfig() {
|
readUIConfig() {
|
||||||
// Check if we have a config.json in the src/assets. If so, use that.
|
// Check if we have a config.json in the src/assets. If so, use that.
|
||||||
// This is where it's written when running "ng e2e" or "yarn serve"
|
// This is where it's written when running "ng e2e" or "yarn serve"
|
||||||
if (fs.existsSync('./src/assets/config.json')) {
|
if (fs.existsSync('./src/assets/config.json')) {
|
||||||
return fs.readFileSync('./src/assets/config.json', 'utf8');
|
return fs.readFileSync('./src/assets/config.json', 'utf8');
|
||||||
// Otherwise, check the dist/browser/assets
|
// Otherwise, check the dist/browser/assets
|
||||||
// This is where it's written when running "serve:ssr", which is what CI uses to start the frontend
|
// This is where it's written when running "serve:ssr", which is what CI uses to start the frontend
|
||||||
} else if (fs.existsSync('./dist/browser/assets/config.json')) {
|
} else if (fs.existsSync('./dist/browser/assets/config.json')) {
|
||||||
return fs.readFileSync('./dist/browser/assets/config.json', 'utf8');
|
return fs.readFileSync('./dist/browser/assets/config.json', 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
// Save value of REST Base URL, looked up before all tests.
|
// Save value of REST Base URL, looked up before all tests.
|
||||||
// This allows other tests to use it easily via getRestBaseURL() below.
|
// This allows other tests to use it easily via getRestBaseURL() below.
|
||||||
saveRestBaseURL(url: string) {
|
saveRestBaseURL(url: string) {
|
||||||
return (REST_BASE_URL = url);
|
return (REST_BASE_URL = url);
|
||||||
},
|
},
|
||||||
// Retrieve currently saved value of REST Base URL
|
// Retrieve currently saved value of REST Base URL
|
||||||
getRestBaseURL() {
|
getRestBaseURL() {
|
||||||
return REST_BASE_URL ;
|
return REST_BASE_URL ;
|
||||||
},
|
},
|
||||||
// Save value of REST Domain, looked up before all tests.
|
// Save value of REST Domain, looked up before all tests.
|
||||||
// This allows other tests to use it easily via getRestBaseDomain() below.
|
// This allows other tests to use it easily via getRestBaseDomain() below.
|
||||||
saveRestBaseDomain(domain: string) {
|
saveRestBaseDomain(domain: string) {
|
||||||
return (REST_DOMAIN = domain);
|
return (REST_DOMAIN = domain);
|
||||||
},
|
},
|
||||||
// Retrieve currently saved value of REST Domain
|
// Retrieve currently saved value of REST Domain
|
||||||
getRestBaseDomain() {
|
getRestBaseDomain() {
|
||||||
return REST_DOMAIN ;
|
return REST_DOMAIN ;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -3,8 +3,14 @@
|
|||||||
// 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,
|
||||||
|
} from 'src/app/core/auth/models/auth-token-info.model';
|
||||||
|
import {
|
||||||
|
DSPACE_XSRF_COOKIE,
|
||||||
|
XSRF_REQUEST_HEADER,
|
||||||
|
} from 'src/app/core/xsrf/xsrf.constants';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
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
|
||||||
@@ -57,33 +63,33 @@ 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 {
|
||||||
// Create a fake CSRF cookie/token to use in POST
|
// Create a fake CSRF cookie/token to use in POST
|
||||||
cy.createCSRFCookie().then((csrfToken: string) => {
|
cy.createCSRFCookie().then((csrfToken: string) => {
|
||||||
// get our REST API's base URL, also needed for POST
|
// get our REST API's base URL, also needed for POST
|
||||||
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
|
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
|
||||||
// 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);
|
||||||
// We expect to have a valid authorization header returned (with our auth token)
|
// We expect to have a valid authorization header returned (with our auth token)
|
||||||
expect(resp.headers).to.have.property('authorization');
|
expect(resp.headers).to.have.property('authorization');
|
||||||
|
|
||||||
// Initialize our AuthTokenInfo object from the authorization header.
|
// Initialize our AuthTokenInfo object from the authorization header.
|
||||||
const authheader = resp.headers.authorization as string;
|
const authheader = resp.headers.authorization as string;
|
||||||
const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader);
|
const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader);
|
||||||
|
|
||||||
// Save our AuthTokenInfo object to our dsAuthInfo UI cookie
|
// Save our AuthTokenInfo object to our dsAuthInfo UI cookie
|
||||||
// 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));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Add as a Cypress command (i.e. assign to 'cy.login')
|
// Add as a Cypress command (i.e. assign to 'cy.login')
|
||||||
Cypress.Commands.add('login', login);
|
Cypress.Commands.add('login', login);
|
||||||
@@ -94,12 +100,12 @@ Cypress.Commands.add('login', login);
|
|||||||
* @param password password to login as
|
* @param password password to login as
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
@@ -117,29 +123,29 @@ 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 {
|
||||||
// Create a fake CSRF cookie/token to use in POST
|
// Create a fake CSRF cookie/token to use in POST
|
||||||
cy.createCSRFCookie().then((csrfToken: string) => {
|
cy.createCSRFCookie().then((csrfToken: string) => {
|
||||||
// get our REST API's base URL, also needed for POST
|
// get our REST API's base URL, also needed for POST
|
||||||
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
|
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
|
||||||
// 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',
|
||||||
url: baseRestUrl + '/api/statistics/viewevents',
|
url: baseRestUrl + '/api/statistics/viewevents',
|
||||||
headers: {
|
headers: {
|
||||||
[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"
|
// 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',
|
'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 },
|
||||||
}).then((resp) => {
|
}).then((resp) => {
|
||||||
// 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);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// 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);
|
||||||
@@ -153,17 +159,17 @@ Cypress.Commands.add('generateViewEvent', generateViewEvent);
|
|||||||
* @returns a Cypress Chainable which can be used to get the generated CSRF Token
|
* @returns a Cypress Chainable which can be used to get the generated CSRF Token
|
||||||
*/
|
*/
|
||||||
function createCSRFCookie(): Cypress.Chainable {
|
function createCSRFCookie(): Cypress.Chainable {
|
||||||
// Generate a new token which is a random UUID
|
// Generate a new token which is a random UUID
|
||||||
const csrfToken: string = uuidv4();
|
const csrfToken: string = uuidv4();
|
||||||
|
|
||||||
// Save it to our required cookie
|
// Save it to our required cookie
|
||||||
cy.task('getRestBaseDomain').then((baseDomain: string) => {
|
cy.task('getRestBaseDomain').then((baseDomain: string) => {
|
||||||
// Create a fake CSRF Token. Set it in the required server-side cookie
|
// Create a fake CSRF Token. Set it in the required server-side cookie
|
||||||
cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain });
|
cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain });
|
||||||
});
|
});
|
||||||
|
|
||||||
// return the generated token wrapped in a chainable
|
// return the generated token wrapped in a chainable
|
||||||
return cy.wrap(csrfToken);
|
return cy.wrap(csrfToken);
|
||||||
}
|
}
|
||||||
// Add as a Cypress command (i.e. assign to 'cy.createCSRFCookie')
|
// Add as a Cypress command (i.e. assign to 'cy.createCSRFCookie')
|
||||||
Cypress.Commands.add('createCSRFCookie', createCSRFCookie);
|
Cypress.Commands.add('createCSRFCookie', createCSRFCookie);
|
||||||
|
@@ -15,10 +15,10 @@
|
|||||||
|
|
||||||
// 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';
|
import { DSPACE_XSRF_COOKIE } from 'src/app/core/xsrf/xsrf.constants';
|
||||||
|
|
||||||
// Runs once before all tests
|
// Runs once before all tests
|
||||||
@@ -34,18 +34,18 @@ before(() => {
|
|||||||
// Find URL of our REST API & save to global variable via task
|
// Find URL of our REST API & save to global variable via task
|
||||||
let baseRestUrl = FALLBACK_TEST_REST_BASE_URL;
|
let baseRestUrl = FALLBACK_TEST_REST_BASE_URL;
|
||||||
if (!config.rest.baseUrl) {
|
if (!config.rest.baseUrl) {
|
||||||
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
|
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
|
||||||
} else {
|
} else {
|
||||||
baseRestUrl = config.rest.baseUrl;
|
baseRestUrl = config.rest.baseUrl;
|
||||||
}
|
}
|
||||||
cy.task('saveRestBaseURL', baseRestUrl);
|
cy.task('saveRestBaseURL', baseRestUrl);
|
||||||
|
|
||||||
// Find domain of our REST API & save to global variable via task.
|
// Find domain of our REST API & save to global variable via task.
|
||||||
let baseDomain = FALLBACK_TEST_REST_DOMAIN;
|
let baseDomain = FALLBACK_TEST_REST_DOMAIN;
|
||||||
if (!config.rest.host) {
|
if (!config.rest.host) {
|
||||||
console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN);
|
console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN);
|
||||||
} else {
|
} else {
|
||||||
baseDomain = config.rest.host;
|
baseDomain = config.rest.host;
|
||||||
}
|
}
|
||||||
cy.task('saveRestBaseDomain', baseDomain);
|
cy.task('saveRestBaseDomain', baseDomain);
|
||||||
|
|
||||||
@@ -54,12 +54,12 @@ before(() => {
|
|||||||
|
|
||||||
// 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 Orejime cookies by setting the orejime-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('orejime-anonymous', '{"authentication":true,"preferences":true,"acknowledgement":true,"google-analytics":true}');
|
||||||
|
|
||||||
// Remove any CSRF cookies saved from prior tests
|
// Remove any CSRF cookies saved from prior tests
|
||||||
cy.clearCookie(DSPACE_XSRF_COOKIE);
|
cy.clearCookie(DSPACE_XSRF_COOKIE);
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL
|
// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL
|
||||||
|
@@ -5,26 +5,26 @@ import { Options } from 'cypress-axe';
|
|||||||
// Uses 'log' and 'table' tasks defined in ../plugins/index.ts
|
// Uses 'log' and 'table' tasks defined in ../plugins/index.ts
|
||||||
// Borrowed from https://github.com/component-driven/cypress-axe#in-your-spec-file
|
// Borrowed from https://github.com/component-driven/cypress-axe#in-your-spec-file
|
||||||
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(
|
||||||
({ id, impact, description, helpUrl, nodes }) => ({
|
({ id, impact, description, helpUrl, nodes }) => ({
|
||||||
id,
|
id,
|
||||||
impact,
|
impact,
|
||||||
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
|
||||||
cy.task('log', violationData);
|
cy.task('log', violationData);
|
||||||
// Optionally, uncomment to print as a table
|
// Optionally, uncomment to print as a table
|
||||||
// cy.task('table', violationData);
|
// cy.task('table', violationData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,13 +32,13 @@ function terminalLog(violations: Result[]) {
|
|||||||
// while also ensuring any violations are logged to the terminal (see terminalLog above)
|
// while also ensuring any violations are logged to the terminal (see terminalLog above)
|
||||||
// This method MUST be called after cy.visit(), as cy.injectAxe() must be called after page load
|
// This method MUST be called after cy.visit(), as cy.injectAxe() must be called after page load
|
||||||
export const testA11y = (context?: any, options?: Options) => {
|
export const testA11y = (context?: any, options?: Options) => {
|
||||||
cy.injectAxe();
|
cy.injectAxe();
|
||||||
cy.configureAxe({
|
cy.configureAxe({
|
||||||
rules: [
|
rules: [
|
||||||
// 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,7 +20,7 @@ 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:latest .
|
docker build -t dspace/dspace-angular:latest .
|
||||||
@@ -46,11 +46,11 @@ 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,
|
||||||
@@ -105,21 +105,21 @@ This allows you to run the Angular UI in *production* mode, pointing it at the d
|
|||||||
(https://demo.dspace.org/server/ or https://sandbox.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,8 +12,6 @@
|
|||||||
# 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"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dspace-cli:
|
dspace-cli:
|
||||||
environment:
|
environment:
|
||||||
|
@@ -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,7 +12,6 @@
|
|||||||
# 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:
|
networks:
|
||||||
# Default to using network named 'dspacenet' from docker-compose-rest.yml.
|
# Default to using network named 'dspacenet' from docker-compose-rest.yml.
|
||||||
# Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet")
|
# Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet")
|
||||||
@@ -22,7 +21,7 @@ networks:
|
|||||||
external: true
|
external: true
|
||||||
services:
|
services:
|
||||||
dspace-cli:
|
dspace-cli:
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}"
|
||||||
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.
|
||||||
|
@@ -12,11 +12,9 @@
|
|||||||
# 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:-latest}-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
|
||||||
@@ -29,23 +27,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,7 +32,8 @@ 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'
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
|
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
||||||
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
networks:
|
networks:
|
||||||
@@ -48,27 +48,31 @@ services:
|
|||||||
# 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:-latest}-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
|
||||||
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:
|
||||||
@@ -77,7 +81,7 @@ services:
|
|||||||
# DSpace Solr container
|
# DSpace Solr container
|
||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
||||||
networks:
|
networks:
|
||||||
- dspacenet
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
@@ -105,6 +109,8 @@ services:
|
|||||||
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
|
precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
|
||||||
cp -r /opt/solr/server/solr/configsets/qaevent/* 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:
|
||||||
|
@@ -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:
|
||||||
@@ -27,7 +26,7 @@ services:
|
|||||||
DSPACE_REST_HOST: sandbox.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_VER:-latest}-dist
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-latest}-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,7 +39,8 @@ 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:-latest-test}"
|
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
||||||
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
networks:
|
networks:
|
||||||
@@ -50,24 +51,27 @@ services:
|
|||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
|
# Keep DSpace assetstore directory between reboots
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
# 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:-latest}"
|
||||||
environment:
|
environment:
|
||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}"
|
POSTGRES_PASSWORD: dspace
|
||||||
networks:
|
networks:
|
||||||
- dspacenet
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
@@ -81,7 +85,7 @@ services:
|
|||||||
# DSpace Solr container
|
# DSpace Solr container
|
||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
||||||
networks:
|
networks:
|
||||||
- dspacenet
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
@@ -97,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'
|
||||||
@@ -113,6 +117,8 @@ services:
|
|||||||
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
|
precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
|
||||||
cp -r /opt/solr/server/solr/configsets/qaevent/* 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:
|
||||||
|
@@ -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_VER:-latest}
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-latest}"
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
@@ -15,7 +15,7 @@ DSPACE_APP_CONFIG_PATH=/usr/local/dspace/config/config.yml
|
|||||||
Configuration options can be overridden by setting environment variables.
|
Configuration options can be overridden by setting environment variables.
|
||||||
|
|
||||||
## Nodejs server
|
## Nodejs server
|
||||||
When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itsself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`.
|
When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`.
|
||||||
|
|
||||||
To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above):
|
To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above):
|
||||||
|
|
||||||
|
4
docs/lint/html/index.md
Normal file
4
docs/lint/html/index.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[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
|
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 {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
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