diff --git a/angular.json b/angular.json index 30fbd6f0bc..95fc8d588a 100644 --- a/angular.json +++ b/angular.json @@ -30,7 +30,6 @@ "src/manifest.json" ], "styles": [ - "node_modules/font-awesome/scss/font-awesome.scss", "src/ndb-theme.scss", "src/styles.scss", "node_modules/flag-icon-css/css/flag-icon.min.css" diff --git a/build/Dockerfile b/build/Dockerfile index 6a09ff2fdc..4328e6b769 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -20,6 +20,9 @@ RUN sed -i "s/appVersion: \".*\"/appVersion: \"$APP_VERSION\"/g" src/environment RUN npm run build-localized +# Merge the service workers for the different locales +RUN node build/merge-service-workers.js + # When set to true, tests are run and coverage will be uploaded to CodeClimate ARG UPLOAD_COVERAGE=false RUN if [ "$UPLOAD_COVERAGE" = true ] ; then \ @@ -55,7 +58,7 @@ ARG SENTRY_ORG ARG SENTRY_PROJECT RUN if [ "$SENTRY_AUTH_TOKEN" != "" ] ; then \ npm install -g @sentry/cli &&\ - sentry-cli --auth-token=$SENTRY_AUTH_TOKEN releases --org=$SENTRY_ORG --project=$SENTRY_PROJECT files $APP_VERSION upload-sourcemaps dist && \ + sentry-cli --auth-token=$SENTRY_AUTH_TOKEN releases --org=$SENTRY_ORG --project=$SENTRY_PROJECT files ndb-core@$APP_VERSION upload-sourcemaps dist && \ rm dist/*/*.map ; fi ### PROD image diff --git a/build/merge-service-workers.js b/build/merge-service-workers.js new file mode 100644 index 0000000000..6d3bbe83b8 --- /dev/null +++ b/build/merge-service-workers.js @@ -0,0 +1,53 @@ +const fs = require("fs"); + +const distFolder = "dist"; + +function getNgswConfig(locale) { + const swConfig = fs.readFileSync(`${distFolder}/${locale}/ngsw.json`); + return JSON.parse(swConfig); +} + +const locales = fs + .readdirSync(distFolder) + .filter((locale) => fs.lstatSync(`${distFolder}/${locale}`).isDirectory()); + +// Merge the ngsw.json files +const firstLocale = locales.pop(); +const combined = getNgswConfig(firstLocale); +locales.forEach((locale) => { + const additional = getNgswConfig(locale); + + // Merge data and asset groups + const toBeMergedGroups = [ + { groupType: "dataGroups", property: "patterns" }, + { groupType: "assetGroups", property: "urls" }, + ]; + toBeMergedGroups.forEach(({ groupType, property }) => { + additional[groupType].forEach((group) => { + combined[groupType] + .find((g) => g.name === group.name) + [property].push(...group[property]); + }); + }); + + // combine hash tables + Object.assign(combined.hashTable, additional.hashTable); + fs.unlinkSync(`${distFolder}/${locale}/ngsw.json`); + fs.unlinkSync(`${distFolder}/${locale}/ngsw-worker.js`); +}); + +combined.index = "/index.html"; + +fs.writeFileSync(`${distFolder}/ngsw.json`, JSON.stringify(combined)); +fs.unlinkSync(`${distFolder}/${firstLocale}/ngsw.json`); + +// Adjust service worker to allow changing language offline +const swFile = fs + .readFileSync(`${distFolder}/${firstLocale}/ngsw-worker.js`) + .toString(); +const patchedSw = swFile.replace( + "return this.handleFetch(this.adapter.newRequest(this.indexUrl), context);", + "return this.handleFetch(this.adapter.newRequest('/' + this.adapter.normalizeUrl(req.url).split('/')[1] + '/index.html'), context);" +); +fs.writeFileSync(`${distFolder}/ngsw-worker.js`, patchedSw); +fs.unlinkSync(`${distFolder}/${firstLocale}/ngsw-worker.js`); diff --git a/doc/compodoc_sources/tutorial/_index.md b/doc/compodoc_sources/tutorial/_index.md index 13c223585d..e3bca569bb 100644 --- a/doc/compodoc_sources/tutorial/_index.md +++ b/doc/compodoc_sources/tutorial/_index.md @@ -8,7 +8,7 @@ We will focus on things specific to our project. But there will be links to other tutorials and resources about the basics of the technologies we use. If anything is unclear or missing, _please_ open an issue on GitHub. -This not only allows us to answer your question but also helps us to improve this document. +Your questions and feedback are very welcome! ----- Let's get started with diff --git a/doc/compodoc_sources/tutorial/setting-up-the-project.md b/doc/compodoc_sources/tutorial/setting-up-the-project.md index 0a743211f1..257e36f442 100644 --- a/doc/compodoc_sources/tutorial/setting-up-the-project.md +++ b/doc/compodoc_sources/tutorial/setting-up-the-project.md @@ -1,9 +1,17 @@ # Tutorial: Setting up the Project -> TODO: the steps from the README should be presented here more step-by-step and beginner friendly +In order to work on our project you should first set up a local development environment. +That way you can run the app on your own computer and immediately see and test changes you make. -for now, please refer to the [README](https://github.com/Aam-Digital/ndb-core/blob/master/README.md) +## Setup +We have documented the steps to clone and set up the project in our [README in the "Development" section](https://github.com/Aam-Digital/ndb-core#development) -Did you manage to run the project locally and visit it at http://localhost:4200/ ? + +## Run +Did you follow the steps in the README and have been able to successfully execute `npm start`? + +Visit your local app in a browser at http://localhost:4200/ + +If you can see and use Aam Digital there, you are good to go. Great! ----- Next up: diff --git a/doc/compodoc_sources/tutorial/technologies.md b/doc/compodoc_sources/tutorial/technologies.md index cb5676aa4e..d0a29d9edf 100644 --- a/doc/compodoc_sources/tutorial/technologies.md +++ b/doc/compodoc_sources/tutorial/technologies.md @@ -1,31 +1,67 @@ # Tutorial: An Overview of our Technologies and Framework -> TODO: this needs to be translated and written in a nice, more elaborate way -Technologien, die wir verwenden: -- Angular (HTML, CSS, TypeScript) - falls du es noch nicht kennst, ist das der beste Startpunkt für dich: Getting Started with Angular: Your First App (eventuell auch bisschen was über HTML und TypeScript lesen) -- Angular Material -- Git (Github) (Unser Repository: https://github.com/Aam-Digital/ndb-core) +![](../../images/tech-stack.png) + +Aam Digital is based on the following technologies: +- [Angular](https://angular.io/), +including the [TypeScript](https://www.typescriptlang.org/) programming language (a superset of JavaScript) +as well as HTML and CSS +- [Angular Material](https://material.angular.io/) providing reusable UI components - PouchDB (CouchDB noSQL Database) +In addition, we use Docker and nginx to deploy and run the application. + +If you are not familiar with (some of) our tools and technologies yet, +here are some useful resources to get started. +You will need to have basic knowledge of these before you can properly start contributing to our project. + +## Git (and GitHub) +Git helps us track all changes to our project, allows us to revert things and is the basis for our code review process. + +Haven't worked with _git_ source control yet? +Work through a basic tutorial like this +[Intro to Git and GitHub for Beginners (Tutorial)](https://product.hubspot.com/blog/git-and-github-tutorial-for-beginners). + +You can also check out the [official git book](https://git-scm.com/book/de/v2) (chapter 1-3 are most relevant). + +## TypeScript +TypeScript is our primary programming language. +If you are familiar with JavaScript, learning TypeScript shouldn't be too hard for you. +All JavaScript code is also valid TypeScript. +TypeScript just adds some things on top to make development easier and less error-prone. + +_(If you don't know JavaScript yet, you can find an endless amount of online courses to learn it - go and do some of these before continuing here!)_ + +If you have some experience with another "statically typed" language like Java, the additional concepts of TypeScript should feel familiar. +A quick comparison like this may be enough to get you a basic understanding: [TypeScript vs. JavaScript](https://medium.com/geekculture/typescript-vs-javascript-e5af7ab5a331) + +## Angular +Angular is a powerful web development framework and the basis for our project. +It provides good structure to build complex enterprise-level web applications +with its concepts of components, services and modules. + +It does take a while to work through Angular's core concepts. +But every minute you invest into understanding these basics now will make your development easier and cleaner forever after. + +Follow the introductory material and tutorial: [Angular - Getting started](https://angular.io/start) -Wie du dich schnell einarbeiten kannst: +You will need a solid understanding of the Angular basics of components, services, dependency injection. +The more advanced topics beyond that are things you can also learn while already working on our project. +We'll be happy to guide you! -- Angular: Falls du Angular (TypeScript Framework von Google) noch nicht kennst, ist das der richtige Einstiegspunkt für dich: - - If you are completely new to Angular, maybe make yourself familiar with the basic structure and approach first: - [Angular - Getting started](https://angular.io/guide/quickstart) - - Angular Heroes Tutorial: [Getting Started with Angular: Your First App](https://angular.io/start) - - Per Buch (in der Bib erhältlich): - - Angular : Grundlagen, fortgeschrittene Techniken und Best Practices mit TypeScript – - - Gregor Woiwode, Ferdinand Malcher, Danny Koppenhagen, Johannes Hoppe +## Angular Material +We are using some standard UI components following the Material Design as building blocks. -- Angular Material (Angular Plugin fürs Styling): - - Einfach ein bisschen durchstöbern: https://material.angular.io/components/categories +Browse through the list of components to get an idea what is already available +(so that we don't reinvent the wheel): +https://material.angular.io/components/categories -- Git (Versionskontrolle): - - Das offizielle Git Buch (Kapitel 1&2 auf Deutsch und Kapitel 1-3 auf Englisch relevant): https://git-scm.com/book/de/v2 +## PouchDB +We have built an abstraction layer of the database access. +Underneath our app is using PouchDB but you do not need any knowledge about this for most work on our project. -- PouchDB (nicht sehr relevant, aber wen es interessiert) - - https://pouchdb.com/learn.html +Curious or want to work on our database services? +Start here: https://pouchdb.com/learn.html ----- Next up: diff --git a/doc/compodoc_sources/tutorial/using-aam-digital.md b/doc/compodoc_sources/tutorial/using-aam-digital.md index bdd9874a79..7ad4e4269f 100644 --- a/doc/compodoc_sources/tutorial/using-aam-digital.md +++ b/doc/compodoc_sources/tutorial/using-aam-digital.md @@ -1,10 +1,21 @@ # Tutorial: Using Aam Digital (as a user) -> TODO: this should maybe include a specific user scenario that also gives a little feeling of achievement -> (maybe adding a child, recording attendance for it and then seeing its absences on the dashboard?) +Before changing things or implementing new features, you should get a basic idea of how Aam Digital currently works for our users. +Feel free to reach out to someone from our team, if you haven't already. We'll be happy to give you a demo and talk about the needs and backgrounds of our users. -To understand the different features and sections a user has in the app, -navigate to the "Help" section in your local instance of _Aam Digital_ -and read through the short summary of features. +To get a true feeling for the app, work through the following short usage scenarios yourself: + +## Basic participants & notes +1. Register a new participant in the system. (Hint: Go through the "Children" list) +2. Add a note about your new participant. (Hint: Either expand the "Notes & Reports" section or use the blue "primary action" button on the bottom right) +3. Find your new note in the project-wide list of notes. (Hint: Navigate to the "Notes" list using the main menu on the left) + +## Recording attendance +1. Add your new participant to a "Recurring Activity". (Hint: open the details of one activity from the "Recurring Activities" list and add the name in the "Participants" section) +2. Record today's attendance for that activity. (Hint: Use "Record Attendance" and find the activity you have just added your participant to) +3. See your participant's attendance record. (Hint: Open the details view for your participant by clicking on the relevant row in the "Children" list and then expand the "Attendance" section) + +## Play around +Try some of the other features and get a feeling for Aam Digital. ----- Next up: diff --git a/doc/images/tech-stack.png b/doc/images/tech-stack.png new file mode 100644 index 0000000000..75803930a8 Binary files /dev/null and b/doc/images/tech-stack.png differ diff --git a/ngsw-config.json b/ngsw-config.json index 5bd386189a..e60e40fd1c 100644 --- a/ngsw-config.json +++ b/ngsw-config.json @@ -7,8 +7,9 @@ "files": [ "/favicon.ico", "/index.html", - "/*.css", "/*.js", + "!/*-es5.*.js", + "/*.css", "/*.woff2", "/*.tff" ] @@ -19,12 +20,14 @@ "updateMode": "prefetch", "resources": { "files": [ - "/assets/*/**", + "/*.svg", "/assets/changelog.json", "/assets/child.png", "/assets/config.default.json", "/assets/How_To.md", - "assets/locale/How_To.de.md" + "/assets/locale/How_To.de.md", + "/assets/*/**", + "/*-es5.*.js" ] } }], diff --git a/package-lock.json b/package-lock.json index 0160334bba..18e191cc99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "ndb-core", "version": "0.0.0", "hasInstallScript": true, "license": "GPL-3.0", @@ -22,14 +23,17 @@ "@angular/platform-browser-dynamic": "^11.2.12", "@angular/router": "^11.2.12", "@angular/service-worker": "^11.2.12", + "@fortawesome/angular-fontawesome": "^0.8.2", + "@fortawesome/fontawesome-svg-core": "^1.2.36", + "@fortawesome/free-regular-svg-icons": "^5.15.2", + "@fortawesome/free-solid-svg-icons": "^5.15.4", "@ngneat/until-destroy": "^8.1.4", - "@sentry/browser": "^6.11.0", + "@sentry/browser": "^6.13.2", "angulartics2": "^10.0.0", "crypto-js": "^4.1.1", "deep-object-diff": "^1.1.0", "faker": "^5.5.3", "flag-icon-css": "^3.5.0", - "font-awesome": "^4.7.0", "idb": "^6.1.2", "json-query": "^2.2.2", "lodash": "^4.17.21", @@ -3727,6 +3731,63 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==", "dev": true }, + "node_modules/@fortawesome/angular-fontawesome": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.8.2.tgz", + "integrity": "sha512-K/AiykA4YbHKE6XKEtZ0ZvVRQocUHyk+79HYWIfhGy3teHpzxsUqB/UjDaxivgBd6dF6ihlzgEbgrDMHlGNwGg==", + "dependencies": { + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@angular/core": "^11.0.0", + "@fortawesome/fontawesome-svg-core": "^1.2.27" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", + "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "1.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", + "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", + "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", + "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -4608,13 +4669,13 @@ "dev": true }, "node_modules/@sentry/browser": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.0.tgz", - "integrity": "sha512-Eh0k2qYhqWiEP3N04AwSrpl4VRD0pzt6SRgJxgiGzSvBT43EOjyNQ3xzMylAechfjSCTWmzZMvwcgT5fNM9cuw==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.2.tgz", + "integrity": "sha512-bkFXK4vAp2UX/4rQY0pj2Iky55Gnwr79CtveoeeMshoLy5iDgZ8gvnLNAz7om4B9OQk1u7NzLEa4IXAmHTUyag==", "dependencies": { - "@sentry/core": "6.13.0", - "@sentry/types": "6.13.0", - "@sentry/utils": "6.13.0", + "@sentry/core": "6.13.2", + "@sentry/types": "6.13.2", + "@sentry/utils": "6.13.2", "tslib": "^1.9.3" }, "engines": { @@ -4627,14 +4688,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/core": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.0.tgz", - "integrity": "sha512-Aw0ljRJx5tq4w6ZXxvcu2Lr9NwD+MJ1SLL5+oB1hh4OlcOJ7OLwDjtJ3+ZOwp75GCAp7phh4+s/Sql6roX3Lpw==", - "dependencies": { - "@sentry/hub": "6.13.0", - "@sentry/minimal": "6.13.0", - "@sentry/types": "6.13.0", - "@sentry/utils": "6.13.0", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.2.tgz", + "integrity": "sha512-snXNNFLwlS7yYxKTX4DBXebvJK+6ikBWN6noQ1CHowvM3ReFBlrdrs0Z0SsSFEzXm2S4q7f6HHbm66GSQZ/8FQ==", + "dependencies": { + "@sentry/hub": "6.13.2", + "@sentry/minimal": "6.13.2", + "@sentry/types": "6.13.2", + "@sentry/utils": "6.13.2", "tslib": "^1.9.3" }, "engines": { @@ -4647,12 +4708,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/hub": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.0.tgz", - "integrity": "sha512-BmKgrTyotF008KPfFt1ySyFg0AAMf/ha9Bz9Rmi+usSJjnOLsObzBJQNAozp4Cu6i1kGvj1/R+ymPCD61Gqozw==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.2.tgz", + "integrity": "sha512-sppSuJdNMiMC/vFm/dQowCBh11uTrmvks00fc190YWgxHshodJwXMdpc+pN61VSOmy2QA4MbQ5aMAgHzPzel3A==", "dependencies": { - "@sentry/types": "6.13.0", - "@sentry/utils": "6.13.0", + "@sentry/types": "6.13.2", + "@sentry/utils": "6.13.2", "tslib": "^1.9.3" }, "engines": { @@ -4665,12 +4726,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/minimal": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.0.tgz", - "integrity": "sha512-eJQs44sGY2wFuVDznHMDeShR+ZbnM/KS+T5753nJ4QtMqCNTiG9WDlNXAxwFJ6Q3DORtaxcWHyFZdOMUusJiZQ==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.2.tgz", + "integrity": "sha512-6iJfEvHzzpGBHDfLxSHcGObh73XU1OSQKWjuhDOe7UQDyI4BQmTfcXAC+Fr8sm8C/tIsmpVi/XJhs8cubFdSMw==", "dependencies": { - "@sentry/hub": "6.13.0", - "@sentry/types": "6.13.0", + "@sentry/hub": "6.13.2", + "@sentry/types": "6.13.2", "tslib": "^1.9.3" }, "engines": { @@ -4683,19 +4744,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/types": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.0.tgz", - "integrity": "sha512-04ZVmz4txuI3w1KS81eByppvvMfOINj7jZYnO5zX/S3cjHiOpAJiZkN/k9tTi1Ua3td8bEkQLB6Cxrq9MSiH3Q==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.2.tgz", + "integrity": "sha512-6WjGj/VjjN8LZDtqJH5ikeB1o39rO1gYS6anBxiS3d0sXNBb3Ux0pNNDFoBxQpOhmdDHXYS57MEptX9EV82gmg==", "engines": { "node": ">=6" } }, "node_modules/@sentry/utils": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.0.tgz", - "integrity": "sha512-e82DBwjYqWkNmafIkHnbqirK4t7WCmqCGaoo1Et6vTRCBS4GthWvS6EzaozY7EKs/TzsfIiDdTLGTbYMQOq9Zw==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.2.tgz", + "integrity": "sha512-foF4PbxqPMWNbuqdXkdoOmKm3quu3PP7Q7j/0pXkri4DtCuvF/lKY92mbY0V9rHS/phCoj+3/Se5JvM2ymh2/w==", "dependencies": { - "@sentry/types": "6.13.0", + "@sentry/types": "6.13.2", "tslib": "^1.9.3" }, "engines": { @@ -14375,14 +14436,6 @@ } } }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=", - "engines": { - "node": ">=0.10.3" - } - }, "node_modules/fontkit": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.8.1.tgz", @@ -34448,6 +34501,43 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==", "dev": true }, + "@fortawesome/angular-fontawesome": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.8.2.tgz", + "integrity": "sha512-K/AiykA4YbHKE6XKEtZ0ZvVRQocUHyk+79HYWIfhGy3teHpzxsUqB/UjDaxivgBd6dF6ihlzgEbgrDMHlGNwGg==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", + "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", + "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + } + }, + "@fortawesome/free-regular-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", + "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", + "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -35131,13 +35221,13 @@ } }, "@sentry/browser": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.0.tgz", - "integrity": "sha512-Eh0k2qYhqWiEP3N04AwSrpl4VRD0pzt6SRgJxgiGzSvBT43EOjyNQ3xzMylAechfjSCTWmzZMvwcgT5fNM9cuw==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.2.tgz", + "integrity": "sha512-bkFXK4vAp2UX/4rQY0pj2Iky55Gnwr79CtveoeeMshoLy5iDgZ8gvnLNAz7om4B9OQk1u7NzLEa4IXAmHTUyag==", "requires": { - "@sentry/core": "6.13.0", - "@sentry/types": "6.13.0", - "@sentry/utils": "6.13.0", + "@sentry/core": "6.13.2", + "@sentry/types": "6.13.2", + "@sentry/utils": "6.13.2", "tslib": "^1.9.3" }, "dependencies": { @@ -35149,14 +35239,14 @@ } }, "@sentry/core": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.0.tgz", - "integrity": "sha512-Aw0ljRJx5tq4w6ZXxvcu2Lr9NwD+MJ1SLL5+oB1hh4OlcOJ7OLwDjtJ3+ZOwp75GCAp7phh4+s/Sql6roX3Lpw==", - "requires": { - "@sentry/hub": "6.13.0", - "@sentry/minimal": "6.13.0", - "@sentry/types": "6.13.0", - "@sentry/utils": "6.13.0", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.2.tgz", + "integrity": "sha512-snXNNFLwlS7yYxKTX4DBXebvJK+6ikBWN6noQ1CHowvM3ReFBlrdrs0Z0SsSFEzXm2S4q7f6HHbm66GSQZ/8FQ==", + "requires": { + "@sentry/hub": "6.13.2", + "@sentry/minimal": "6.13.2", + "@sentry/types": "6.13.2", + "@sentry/utils": "6.13.2", "tslib": "^1.9.3" }, "dependencies": { @@ -35168,12 +35258,12 @@ } }, "@sentry/hub": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.0.tgz", - "integrity": "sha512-BmKgrTyotF008KPfFt1ySyFg0AAMf/ha9Bz9Rmi+usSJjnOLsObzBJQNAozp4Cu6i1kGvj1/R+ymPCD61Gqozw==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.2.tgz", + "integrity": "sha512-sppSuJdNMiMC/vFm/dQowCBh11uTrmvks00fc190YWgxHshodJwXMdpc+pN61VSOmy2QA4MbQ5aMAgHzPzel3A==", "requires": { - "@sentry/types": "6.13.0", - "@sentry/utils": "6.13.0", + "@sentry/types": "6.13.2", + "@sentry/utils": "6.13.2", "tslib": "^1.9.3" }, "dependencies": { @@ -35185,12 +35275,12 @@ } }, "@sentry/minimal": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.0.tgz", - "integrity": "sha512-eJQs44sGY2wFuVDznHMDeShR+ZbnM/KS+T5753nJ4QtMqCNTiG9WDlNXAxwFJ6Q3DORtaxcWHyFZdOMUusJiZQ==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.2.tgz", + "integrity": "sha512-6iJfEvHzzpGBHDfLxSHcGObh73XU1OSQKWjuhDOe7UQDyI4BQmTfcXAC+Fr8sm8C/tIsmpVi/XJhs8cubFdSMw==", "requires": { - "@sentry/hub": "6.13.0", - "@sentry/types": "6.13.0", + "@sentry/hub": "6.13.2", + "@sentry/types": "6.13.2", "tslib": "^1.9.3" }, "dependencies": { @@ -35202,16 +35292,16 @@ } }, "@sentry/types": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.0.tgz", - "integrity": "sha512-04ZVmz4txuI3w1KS81eByppvvMfOINj7jZYnO5zX/S3cjHiOpAJiZkN/k9tTi1Ua3td8bEkQLB6Cxrq9MSiH3Q==" + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.2.tgz", + "integrity": "sha512-6WjGj/VjjN8LZDtqJH5ikeB1o39rO1gYS6anBxiS3d0sXNBb3Ux0pNNDFoBxQpOhmdDHXYS57MEptX9EV82gmg==" }, "@sentry/utils": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.0.tgz", - "integrity": "sha512-e82DBwjYqWkNmafIkHnbqirK4t7WCmqCGaoo1Et6vTRCBS4GthWvS6EzaozY7EKs/TzsfIiDdTLGTbYMQOq9Zw==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.2.tgz", + "integrity": "sha512-foF4PbxqPMWNbuqdXkdoOmKm3quu3PP7Q7j/0pXkri4DtCuvF/lKY92mbY0V9rHS/phCoj+3/Se5JvM2ymh2/w==", "requires": { - "@sentry/types": "6.13.0", + "@sentry/types": "6.13.2", "tslib": "^1.9.3" }, "dependencies": { @@ -42884,11 +42974,6 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" }, - "font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" - }, "fontkit": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.8.1.tgz", diff --git a/package.json b/package.json index 62444a83e0..1fae8252e3 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,17 @@ "@angular/platform-browser-dynamic": "^11.2.12", "@angular/router": "^11.2.12", "@angular/service-worker": "^11.2.12", + "@fortawesome/angular-fontawesome": "^0.8.2", + "@fortawesome/fontawesome-svg-core": "^1.2.36", + "@fortawesome/free-regular-svg-icons": "^5.15.2", + "@fortawesome/free-solid-svg-icons": "^5.15.4", "@ngneat/until-destroy": "^8.1.4", - "@sentry/browser": "^6.11.0", + "@sentry/browser": "^6.13.2", "angulartics2": "^10.0.0", "crypto-js": "^4.1.1", "deep-object-diff": "^1.1.0", "faker": "^5.5.3", "flag-icon-css": "^3.5.0", - "font-awesome": "^4.7.0", "idb": "^6.1.2", "json-query": "^2.2.2", "lodash": "^4.17.21", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 072c96af76..b065887774 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -65,7 +65,6 @@ import { EntitySubrecordModule } from "./core/entity-components/entity-subrecord import { EntityListModule } from "./core/entity-components/entity-list/entity-list.module"; import { AttendanceModule } from "./child-dev-project/attendance/attendance.module"; import { DemoActivityGeneratorService } from "./child-dev-project/attendance/demo-data/demo-activity-generator.service"; -import { FontAwesomeIconsModule } from "./core/icons/font-awesome-icons.module"; import { ConfigurableEnumModule } from "./core/configurable-enum/configurable-enum.module"; import { ConfigModule } from "./core/config/config.module"; import { DemoActivityEventsGeneratorService } from "./child-dev-project/attendance/demo-data/demo-activity-events-generator.service"; @@ -76,6 +75,9 @@ import { HistoricalDataModule } from "./features/historical-data/historical-data import { EntityUtilsModule } from "./core/entity-components/entity-utils/entity-utils.module"; import { DemoHistoricalDataGenerator } from "./features/historical-data/demo-historical-data-generator"; import { TranslatableMatPaginator } from "./core/translation/TranslatableMatPaginator"; +import { FaIconLibrary } from "@fortawesome/angular-fontawesome"; +import { fas } from "@fortawesome/free-solid-svg-icons"; +import { far } from "@fortawesome/free-regular-svg-icons"; /** * Main entry point of the application. @@ -85,7 +87,7 @@ import { TranslatableMatPaginator } from "./core/translation/TranslatableMatPagi @NgModule({ declarations: [AppComponent], imports: [ - ServiceWorkerModule.register("ngsw-worker.js", { + ServiceWorkerModule.register("/ngsw-worker.js", { enabled: environment.production, }), Angulartics2Module.forRoot({ @@ -115,7 +117,6 @@ import { TranslatableMatPaginator } from "./core/translation/TranslatableMatPagi ChildrenModule, SchoolsModule, AdminModule, - FontAwesomeIconsModule, MarkdownPageModule, EntitySubrecordModule, EntityListModule, @@ -159,7 +160,11 @@ import { TranslatableMatPaginator } from "./core/translation/TranslatableMatPagi ], bootstrap: [AppComponent], }) -export class AppModule {} +export class AppModule { + constructor(icons: FaIconLibrary) { + icons.addIconPacks(fas, far); + } +} // Initialize remote logging LoggingService.initRemoteLogging({ diff --git a/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.stories.ts b/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.stories.ts index e880dd80df..4aa131dc84 100644 --- a/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.stories.ts +++ b/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.stories.ts @@ -3,7 +3,6 @@ import { moduleMetadata } from "@storybook/angular"; import { RecurringActivity } from "../model/recurring-activity"; import { ActivityAttendanceSectionComponent } from "./activity-attendance-section.component"; import { AttendanceModule } from "../attendance.module"; -import { FontAwesomeIconsModule } from "../../../core/icons/font-awesome-icons.module"; import { RouterTestingModule } from "@angular/router/testing"; import { AttendanceService } from "../attendance.service"; import { @@ -74,7 +73,6 @@ export default { imports: [ AttendanceModule, EntitySubrecordModule, - FontAwesomeIconsModule, RouterTestingModule, MatNativeDateModule, Angulartics2Module.forRoot(), diff --git a/src/app/child-dev-project/attendance/activity-card/activity-card.component.html b/src/app/child-dev-project/attendance/activity-card/activity-card.component.html index 20255b5d5f..3520fd08be 100644 --- a/src/app/child-dev-project/attendance/activity-card/activity-card.component.html +++ b/src/app/child-dev-project/attendance/activity-card/activity-card.component.html @@ -5,24 +5,24 @@ }" > - - - + - + {{ event?.subject }} diff --git a/src/app/child-dev-project/attendance/activity-card/activity-card.component.spec.ts b/src/app/child-dev-project/attendance/activity-card/activity-card.component.spec.ts index 1ccc9851ab..bededc14fa 100644 --- a/src/app/child-dev-project/attendance/activity-card/activity-card.component.spec.ts +++ b/src/app/child-dev-project/attendance/activity-card/activity-card.component.spec.ts @@ -2,7 +2,6 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { ActivityCardComponent } from "./activity-card.component"; import { MatCardModule } from "@angular/material/card"; -import { FontAwesomeIconsModule } from "../../../core/icons/font-awesome-icons.module"; import { MatTooltipModule } from "@angular/material/tooltip"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { Note } from "../../notes/model/note"; @@ -15,12 +14,7 @@ describe("ActivityCardComponent", () => { waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ActivityCardComponent], - imports: [ - MatCardModule, - MatTooltipModule, - NoopAnimationsModule, - FontAwesomeIconsModule, - ], + imports: [MatCardModule, MatTooltipModule, NoopAnimationsModule], }).compileComponents(); }) ); diff --git a/src/app/child-dev-project/attendance/activity-card/activity-card.stories.ts b/src/app/child-dev-project/attendance/activity-card/activity-card.stories.ts index bda6cf1bc1..07379f1f64 100644 --- a/src/app/child-dev-project/attendance/activity-card/activity-card.stories.ts +++ b/src/app/child-dev-project/attendance/activity-card/activity-card.stories.ts @@ -6,7 +6,6 @@ import { DemoChildGenerator } from "../../children/demo-data-generators/demo-chi import { addDefaultChildPhoto } from "../../../../../.storybook/utils/addDefaultChildPhoto"; import { MatCardModule } from "@angular/material/card"; import { RecurringActivity } from "../model/recurring-activity"; -import { FontAwesomeIconsModule } from "../../../core/icons/font-awesome-icons.module"; import { MatTooltipModule } from "@angular/material/tooltip"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @@ -15,12 +14,7 @@ export default { component: ActivityCardComponent, decorators: [ moduleMetadata({ - imports: [ - MatCardModule, - MatTooltipModule, - BrowserAnimationsModule, - FontAwesomeIconsModule, - ], + imports: [MatCardModule, MatTooltipModule, BrowserAnimationsModule], }), ], } as Meta; diff --git a/src/app/child-dev-project/attendance/activity-list/activity-list.component.spec.ts b/src/app/child-dev-project/attendance/activity-list/activity-list.component.spec.ts index 4e2a6f0ffd..a3f864e6f7 100644 --- a/src/app/child-dev-project/attendance/activity-list/activity-list.component.spec.ts +++ b/src/app/child-dev-project/attendance/activity-list/activity-list.component.spec.ts @@ -9,6 +9,7 @@ import { Angulartics2Module } from "angulartics2"; import { EntityListConfig } from "../../../core/entity-components/entity-list/EntityListConfig"; import { ExportService } from "../../../core/export/export-service/export.service"; import { MockSessionModule } from "../../../core/session/mock-session.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ActivityListComponent", () => { let component: ActivityListComponent; @@ -27,6 +28,7 @@ describe("ActivityListComponent", () => { RouterTestingModule, Angulartics2Module.forRoot(), MockSessionModule.withState(), + FontAwesomeTestingModule, ], providers: [ { provide: ExportService, useValue: {} }, diff --git a/src/app/child-dev-project/attendance/add-day-attendance/roll-call-setup/roll-call-setup.stories.ts b/src/app/child-dev-project/attendance/add-day-attendance/roll-call-setup/roll-call-setup.stories.ts index 474c9dec48..a634f5b6f9 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/roll-call-setup/roll-call-setup.stories.ts +++ b/src/app/child-dev-project/attendance/add-day-attendance/roll-call-setup/roll-call-setup.stories.ts @@ -16,7 +16,6 @@ import { ReactiveFormsModule } from "@angular/forms"; import { MatStepperModule } from "@angular/material/stepper"; import { ChildrenService } from "../../../children/children.service"; import { MatSelectModule } from "@angular/material/select"; -import { MatIconModule } from "@angular/material/icon"; import { MatListModule } from "@angular/material/list"; import { AttendanceService } from "../../attendance.service"; import moment from "moment"; @@ -28,7 +27,6 @@ import { MatCardModule } from "@angular/material/card"; import { Note } from "../../../notes/model/note"; import { ActivityCardComponent } from "../../activity-card/activity-card.component"; import { MatTooltipModule } from "@angular/material/tooltip"; -import { FontAwesomeIconsModule } from "../../../../core/icons/font-awesome-icons.module"; import { DemoActivityGeneratorService } from "../../demo-data/demo-activity-generator.service"; import { FormDialogModule } from "../../../../core/form-dialog/form-dialog.module"; import { PouchDatabase } from "../../../../core/database/pouch-database"; @@ -80,11 +78,9 @@ export default { MatNativeDateModule, MatStepperModule, MatSelectModule, - MatIconModule, MatListModule, MatCardModule, MatTooltipModule, - FontAwesomeIconsModule, ReactiveFormsModule, FlexLayoutModule, EntityModule, diff --git a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.html b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.html index ffb68bcc0c..b73cf54ef3 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.html +++ b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.html @@ -25,10 +25,10 @@ : '' " > - + icon="check" + > {{ option.label }} @@ -36,7 +36,7 @@
- +
{ let component: RollCallComponent; @@ -42,7 +43,11 @@ describe("RollCallComponent", () => { mockLoggingService = jasmine.createSpyObj(["warn"]); TestBed.configureTestingModule({ - imports: [AttendanceModule, MockSessionModule], + imports: [ + AttendanceModule, + MockSessionModule, + FontAwesomeTestingModule, + ], providers: [ { provide: ConfigService, useValue: mockConfigService }, { provide: EntityMapperService, useValue: mockEntityMapper }, diff --git a/src/app/child-dev-project/attendance/attendance-calendar/attendance-calendar.component.html b/src/app/child-dev-project/attendance/attendance-calendar/attendance-calendar.component.html index 742f474886..a980d4d0ea 100644 --- a/src/app/child-dev-project/attendance/attendance-calendar/attendance-calendar.component.html +++ b/src/app/child-dev-project/attendance/attendance-calendar/attendance-calendar.component.html @@ -12,7 +12,7 @@

diff --git a/src/app/child-dev-project/attendance/attendance-calendar/attendance-calendar.stories.ts b/src/app/child-dev-project/attendance/attendance-calendar/attendance-calendar.stories.ts index 7c03354a24..d3f5e1cc72 100644 --- a/src/app/child-dev-project/attendance/attendance-calendar/attendance-calendar.stories.ts +++ b/src/app/child-dev-project/attendance/attendance-calendar/attendance-calendar.stories.ts @@ -1,7 +1,6 @@ import { Story, Meta } from "@storybook/angular/types-6-0"; import { moduleMetadata } from "@storybook/angular"; import { AttendanceModule } from "../attendance.module"; -import { FontAwesomeIconsModule } from "../../../core/icons/font-awesome-icons.module"; import { RouterTestingModule } from "@angular/router/testing"; import { generateEventWithAttendance } from "../model/activity-attendance"; import { AttendanceLogicalStatus } from "../model/attendance-status"; @@ -57,7 +56,6 @@ export default { imports: [ AttendanceModule, FormDialogModule, - FontAwesomeIconsModule, RouterTestingModule, MatNativeDateModule, ], diff --git a/src/app/child-dev-project/attendance/attendance-details/attendance-details.stories.ts b/src/app/child-dev-project/attendance/attendance-details/attendance-details.stories.ts index c31efea87e..e1d6207e53 100644 --- a/src/app/child-dev-project/attendance/attendance-details/attendance-details.stories.ts +++ b/src/app/child-dev-project/attendance/attendance-details/attendance-details.stories.ts @@ -11,7 +11,6 @@ import { AttendanceModule } from "../attendance.module"; import { RouterTestingModule } from "@angular/router/testing"; import { FormDialogModule } from "../../../core/form-dialog/form-dialog.module"; import { Angulartics2Module } from "angulartics2"; -import { FontAwesomeIconsModule } from "../../../core/icons/font-awesome-icons.module"; import { MatNativeDateModule } from "@angular/material/core"; import { EntitySubrecordModule } from "../../../core/entity-components/entity-subrecord/entity-subrecord.module"; import { MatDialogRef } from "@angular/material/dialog"; @@ -64,7 +63,6 @@ export default { AttendanceModule, EntitySubrecordModule, FormDialogModule, - FontAwesomeIconsModule, RouterTestingModule, MatNativeDateModule, NotesModule, diff --git a/src/app/child-dev-project/attendance/attendance-status-select/attendance-status-select.stories.ts b/src/app/child-dev-project/attendance/attendance-status-select/attendance-status-select.stories.ts index 07dafc3349..f3cf0754e8 100644 --- a/src/app/child-dev-project/attendance/attendance-status-select/attendance-status-select.stories.ts +++ b/src/app/child-dev-project/attendance/attendance-status-select/attendance-status-select.stories.ts @@ -1,7 +1,6 @@ import { Story, Meta } from "@storybook/angular/types-6-0"; import { moduleMetadata } from "@storybook/angular"; import { AttendanceModule } from "../attendance.module"; -import { FontAwesomeIconsModule } from "../../../core/icons/font-awesome-icons.module"; import { RouterTestingModule } from "@angular/router/testing"; import { MatNativeDateModule } from "@angular/material/core"; import { FormDialogModule } from "../../../core/form-dialog/form-dialog.module"; @@ -16,7 +15,6 @@ export default { imports: [ AttendanceModule, FormDialogModule, - FontAwesomeIconsModule, RouterTestingModule, MatNativeDateModule, ], diff --git a/src/app/child-dev-project/attendance/attendance.module.ts b/src/app/child-dev-project/attendance/attendance.module.ts index a24fe8261d..e188cee5f3 100644 --- a/src/app/child-dev-project/attendance/attendance.module.ts +++ b/src/app/child-dev-project/attendance/attendance.module.ts @@ -23,7 +23,6 @@ import { MatButtonModule } from "@angular/material/button"; import { CommonModule } from "@angular/common"; import { ActivityCardComponent } from "./activity-card/activity-card.component"; import { MatCardModule } from "@angular/material/card"; -import { MatIconModule } from "@angular/material/icon"; import { MatTooltipModule } from "@angular/material/tooltip"; import { RollCallSetupComponent } from "./add-day-attendance/roll-call-setup/roll-call-setup.component"; import { MatListModule } from "@angular/material/list"; @@ -57,6 +56,7 @@ import { Angulartics2Module } from "angulartics2"; import { MatSlideToggleModule } from "@angular/material/slide-toggle"; import { AttendanceManagerComponent } from "./attendance-manager/attendance-manager.component"; import { EntityUtilsModule } from "../../core/entity-components/entity-utils/entity-utils.module"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; @NgModule({ declarations: [ @@ -82,7 +82,6 @@ import { EntityUtilsModule } from "../../core/entity-components/entity-utils/ent MatButtonModule, CommonModule, MatCardModule, - MatIconModule, MatTooltipModule, MatListModule, MatInputModule, @@ -103,6 +102,7 @@ import { EntityUtilsModule } from "../../core/entity-components/entity-utils/ent Angulartics2Module, MatSlideToggleModule, EntityUtilsModule, + FontAwesomeModule, ], exports: [ ActivityCardComponent, diff --git a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.html b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.html index e643d22a8a..a4315d2b58 100644 --- a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.html +++ b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.html @@ -1,7 +1,7 @@
- +
{{ dashboardRowGroups?.length }} diff --git a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.spec.ts b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.spec.ts index 447acf9f60..8067b6671a 100644 --- a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.spec.ts +++ b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.component.spec.ts @@ -5,6 +5,7 @@ import { RouterTestingModule } from "@angular/router/testing"; import { ChildPhotoService } from "../../../children/child-photo-service/child-photo.service"; import { AttendanceModule } from "../../attendance.module"; import { AttendanceService } from "../../attendance.service"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("AttendanceWeekDashboardComponent", () => { let component: AttendanceWeekDashboardComponent; @@ -20,7 +21,11 @@ describe("AttendanceWeekDashboardComponent", () => { [] ); TestBed.configureTestingModule({ - imports: [AttendanceModule, RouterTestingModule.withRoutes([])], + imports: [ + AttendanceModule, + RouterTestingModule.withRoutes([]), + FontAwesomeTestingModule, + ], providers: [ { provide: ChildPhotoService, diff --git a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.stories.ts b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.stories.ts index 48184e1883..37217a61e0 100644 --- a/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.stories.ts +++ b/src/app/child-dev-project/attendance/dashboard-widgets/attendance-week-dashboard/attendance-week-dashboard.stories.ts @@ -3,7 +3,6 @@ import { moduleMetadata } from "@storybook/angular"; import { AttendanceWeekDashboardComponent } from "./attendance-week-dashboard.component"; import { AttendanceModule } from "../../attendance.module"; import { RecurringActivity } from "../../model/recurring-activity"; -import { FontAwesomeIconsModule } from "../../../../core/icons/font-awesome-icons.module"; import { Child } from "../../../children/model/child"; import { generateEventWithAttendance } from "../../model/activity-attendance"; import { AttendanceLogicalStatus } from "../../model/attendance-status"; @@ -58,7 +57,6 @@ export default { moduleMetadata({ imports: [ AttendanceModule, - FontAwesomeIconsModule, RouterTestingModule, Angulartics2Module.forRoot(), ], diff --git a/src/app/child-dev-project/children/child-block/child-block.component.html b/src/app/child-dev-project/children/child-block/child-block.component.html index 8be114a9b7..cec854c1f8 100644 --- a/src/app/child-dev-project/children/child-block/child-block.component.html +++ b/src/app/child-dev-project/children/child-block/child-block.component.html @@ -25,7 +25,7 @@

{{ entity?.name }}

-

{{ entity?.phone }}

+

{{ entity?.phone }}

, diff --git a/src/app/child-dev-project/children/child-block/child-block.component.spec.ts b/src/app/child-dev-project/children/child-block/child-block.component.spec.ts index a09678f921..fe2421295e 100644 --- a/src/app/child-dev-project/children/child-block/child-block.component.spec.ts +++ b/src/app/child-dev-project/children/child-block/child-block.component.spec.ts @@ -2,7 +2,6 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { ChildBlockComponent } from "./child-block.component"; import { RouterTestingModule } from "@angular/router/testing"; -import { MatIconModule } from "@angular/material/icon"; import { of } from "rxjs"; import { SchoolBlockComponent } from "../../schools/school-block/school-block.component"; import { ChildrenService } from "../children.service"; @@ -22,7 +21,7 @@ describe("ChildBlockComponent", () => { TestBed.configureTestingModule({ declarations: [SchoolBlockComponent, ChildBlockComponent], - imports: [RouterTestingModule, MatIconModule], + imports: [RouterTestingModule], providers: [ { provide: ChildrenService, useValue: mockChildrenService }, ], diff --git a/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html b/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html index d2afc13f3b..a18c2bd1ec 100644 --- a/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html +++ b/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html @@ -1,7 +1,7 @@

- +
{{ bmiRows?.length }} diff --git a/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.spec.ts b/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.spec.ts index 103bf8f4f0..24c50fe9af 100644 --- a/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.spec.ts +++ b/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.spec.ts @@ -6,6 +6,7 @@ import { ChildrenService } from "../children.service"; import { Child } from "../model/child"; import { ChildrenBmiDashboardComponent } from "./children-bmi-dashboard.component"; import { ChildrenModule } from "../children.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ChildrenBmiDashboardComponent", () => { let component: ChildrenBmiDashboardComponent; @@ -18,7 +19,11 @@ describe("ChildrenBmiDashboardComponent", () => { beforeEach(() => { mockChildrenService.getChildren.and.returnValue(of([])); TestBed.configureTestingModule({ - imports: [ChildrenModule, RouterTestingModule.withRoutes([])], + imports: [ + ChildrenModule, + RouterTestingModule.withRoutes([]), + FontAwesomeTestingModule, + ], providers: [{ provide: ChildrenService, useValue: mockChildrenService }], }).compileComponents(); }); diff --git a/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html b/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html index 9246d12857..0df9fd118a 100644 --- a/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html +++ b/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html @@ -1,7 +1,7 @@
- +
{{ totalChildren }} @@ -23,7 +23,7 @@ {{ entry.label }} {{ entry.value }} - + diff --git a/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.spec.ts b/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.spec.ts index 02bb7cf950..5cad0f0300 100644 --- a/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.spec.ts +++ b/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.spec.ts @@ -8,12 +8,12 @@ import { import { ChildrenCountDashboardComponent } from "./children-count-dashboard.component"; import { MatCardModule } from "@angular/material/card"; -import { MatIconModule } from "@angular/material/icon"; import { ChildrenService } from "../children.service"; import { RouterTestingModule } from "@angular/router/testing"; import { Center, Child } from "../model/child"; import { Observable } from "rxjs"; import { ConfigurableEnumValue } from "../../../core/configurable-enum/configurable-enum.interface"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ChildrenCountDashboardComponent", () => { let component: ChildrenCountDashboardComponent; @@ -41,7 +41,7 @@ describe("ChildrenCountDashboardComponent", () => { TestBed.configureTestingModule({ declarations: [ChildrenCountDashboardComponent], - imports: [MatIconModule, MatCardModule, RouterTestingModule], + imports: [MatCardModule, RouterTestingModule, FontAwesomeTestingModule], providers: [{ provide: ChildrenService, useValue: childrenService }], }).compileComponents(); }) diff --git a/src/app/child-dev-project/children/children-list/children-list.component.spec.ts b/src/app/child-dev-project/children/children-list/children-list.component.spec.ts index caee0ec0b0..3111b9cd9a 100644 --- a/src/app/child-dev-project/children/children-list/children-list.component.spec.ts +++ b/src/app/child-dev-project/children/children-list/children-list.component.spec.ts @@ -23,6 +23,7 @@ import { School } from "../../schools/model/school"; import { LoggingService } from "../../../core/logging/logging.service"; import { ExportService } from "../../../core/export/export-service/export.service"; import { MockSessionModule } from "../../../core/session/mock-session.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ChildrenListComponent", () => { let component: ChildrenListComponent; @@ -94,6 +95,7 @@ describe("ChildrenListComponent", () => { RouterTestingModule, Angulartics2Module.forRoot(), MockSessionModule.withState(), + FontAwesomeTestingModule, ], providers: [ { diff --git a/src/app/child-dev-project/children/children.module.ts b/src/app/child-dev-project/children/children.module.ts index 399b2662f6..6b731dae04 100644 --- a/src/app/child-dev-project/children/children.module.ts +++ b/src/app/child-dev-project/children/children.module.ts @@ -28,7 +28,6 @@ import { MatButtonToggleModule } from "@angular/material/button-toggle"; import { MatCardModule } from "@angular/material/card"; import { MatCheckboxModule } from "@angular/material/checkbox"; import { MatDialogModule } from "@angular/material/dialog"; -import { MatIconModule } from "@angular/material/icon"; import { MatListModule } from "@angular/material/list"; import { MatProgressBarModule } from "@angular/material/progress-bar"; import { MatSidenavModule } from "@angular/material/sidenav"; @@ -67,6 +66,7 @@ import { ChildrenBmiDashboardComponent } from "./children-bmi-dashboard/children import { EntitySchemaService } from "../../core/entity/schema/entity-schema.service"; import { PhotoDatatype } from "./child-photo-service/datatype-photo"; import { EntityUtilsModule } from "../../core/entity-components/entity-utils/entity-utils.module"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; @NgModule({ imports: [ @@ -86,7 +86,6 @@ import { EntityUtilsModule } from "../../core/entity-components/entity-utils/ent MatSidenavModule, MatButtonModule, MatButtonToggleModule, - MatIconModule, MatCardModule, MatSnackBarModule, MatProgressBarModule, @@ -108,6 +107,7 @@ import { EntityUtilsModule } from "../../core/entity-components/entity-utils/ent EntitySubrecordModule, EntityListModule, EntityUtilsModule, + FontAwesomeModule, ], declarations: [ ChildBlockComponent, diff --git a/src/app/child-dev-project/children/children.service.ts b/src/app/child-dev-project/children/children.service.ts index f89802e363..2aa96fd264 100644 --- a/src/app/child-dev-project/children/children.service.ts +++ b/src/app/child-dev-project/children/children.service.ts @@ -405,16 +405,11 @@ export class ChildrenService { return this.dbIndexing.createIndex(designDoc); } - getEducationalMaterialsOfChild( + async getEducationalMaterialsOfChild( childId: string - ): Observable { - return from( - this.entityMapper - .loadType(EducationalMaterial) - .then((loadedEntities) => { - return loadedEntities.filter((o) => o.child === childId); - }) - ); + ): Promise { + const allMaterials = await this.entityMapper.loadType(EducationalMaterial); + return allMaterials.filter((mat) => mat.child === childId); } /** diff --git a/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.html b/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.html index 7f6adcf40a..a70c434574 100644 --- a/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.html +++ b/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.html @@ -1,7 +1,7 @@ diff --git a/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.spec.ts b/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.spec.ts index 71f5eb8679..6220bf0920 100644 --- a/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.spec.ts +++ b/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.spec.ts @@ -5,26 +5,30 @@ import { ChildrenService } from "../../children/children.service"; import { Child } from "../../children/model/child"; import { DatePipe } from "@angular/common"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; -import { of } from "rxjs"; import { ChildrenModule } from "../../children/children.module"; import { MockSessionModule } from "../../../core/session/mock-session.module"; +import { EducationalMaterial } from "../model/educational-material"; +import { ConfigurableEnumValue } from "../../../core/configurable-enum/configurable-enum.interface"; describe("EducationalMaterialComponent", () => { let component: EducationalMaterialComponent; let fixture: ComponentFixture; let mockChildrenService: jasmine.SpyObj; const child = new Child("22"); + const PENCIL: ConfigurableEnumValue = { + id: "pencil", + label: "Pencil", + }; + const RULER: ConfigurableEnumValue = { + id: "ruler", + label: "Ruler", + }; beforeEach( waitForAsync(() => { mockChildrenService = jasmine.createSpyObj([ - "getChild", "getEducationalMaterialsOfChild", ]); - mockChildrenService.getChild.and.returnValue(of(child)); - mockChildrenService.getEducationalMaterialsOfChild.and.returnValue( - of([]) - ); TestBed.configureTestingModule({ declarations: [EducationalMaterialComponent], imports: [ @@ -50,4 +54,58 @@ describe("EducationalMaterialComponent", () => { it("should create", () => { expect(component).toBeTruthy(); }); + + it("produces an empty summary when there are no records", () => { + component.records = []; + component.updateSummary(); + expect(component.summary).toHaveSize(0); + }); + + function setRecordsAndGenerateSummary( + ...records: Partial[] + ) { + component.records = records.map(EducationalMaterial.create); + component.updateSummary(); + } + + it("produces a singleton summary when there is a single record", () => { + setRecordsAndGenerateSummary({ materialType: PENCIL, materialAmount: 1 }); + expect(component.summary).toEqual(`${PENCIL.label}: 1`); + }); + + it("produces a summary of all records when they are all different", () => { + setRecordsAndGenerateSummary( + { materialType: PENCIL, materialAmount: 2 }, + { materialType: RULER, materialAmount: 1 } + ); + expect(component.summary).toEqual(`${PENCIL.label}: 2, ${RULER.label}: 1`); + }); + + it("produces a summary of all records when there are duplicates", () => { + setRecordsAndGenerateSummary( + { materialType: PENCIL, materialAmount: 1 }, + { materialType: RULER, materialAmount: 1 }, + { materialType: PENCIL, materialAmount: 3 } + ); + expect(component.summary).toEqual(`${PENCIL.label}: 4, ${RULER.label}: 1`); + }); + + it("loads all education data associated with a child and updates the summary", async () => { + const educationalData = [ + { materialType: PENCIL, materialAmount: 1 }, + { materialType: RULER, materialAmount: 2 }, + ].map(EducationalMaterial.create); + mockChildrenService.getEducationalMaterialsOfChild.and.resolveTo( + educationalData + ); + await component.loadData("22"); + expect(component.summary).toEqual(`${PENCIL.label}: 1, ${RULER.label}: 2`); + expect(component.records).toEqual(educationalData); + }); + + it("associates a new record with the current child", () => { + component.child = child; + const newRecord = component.newRecordFactory(); + expect(newRecord.child).toBe(child.getId()); + }); }); diff --git a/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.ts b/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.ts index e2e838d509..de7d68be6b 100644 --- a/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.ts +++ b/src/app/child-dev-project/educational-material/educational-material-component/educational-material.component.ts @@ -1,13 +1,15 @@ import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"; import { EducationalMaterial } from "../model/educational-material"; import { ChildrenService } from "../../children/children.service"; -import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; import { Child } from "../../children/model/child"; import { OnInitDynamicComponent } from "../../../core/view/dynamic-components/on-init-dynamic-component.interface"; import { PanelConfig } from "../../../core/entity-components/entity-details/EntityDetailsConfig"; import { FormFieldConfig } from "../../../core/entity-components/entity-form/entity-form/FormConfig"; -@UntilDestroy() +/** + * Displays educational materials of a child, such as a pencil, rulers, e.t.c + * as well as a summary + */ @Component({ selector: "app-educational-material", templateUrl: "./educational-material.component.html", @@ -15,7 +17,7 @@ import { FormFieldConfig } from "../../../core/entity-components/entity-form/ent export class EducationalMaterialComponent implements OnChanges, OnInitDynamicComponent { @Input() child: Child; - records = new Array(); + records: EducationalMaterial[] = []; summary = ""; columns: FormFieldConfig[] = [ @@ -27,46 +29,48 @@ export class EducationalMaterialComponent constructor(private childrenService: ChildrenService) {} - ngOnChanges(changes: SimpleChanges): void { + async ngOnChanges(changes: SimpleChanges): Promise { if (changes.hasOwnProperty("child")) { - this.loadData(this.child.getId()); + await this.loadData(this.child.getId()); } } - onInitFromDynamicConfig(config: PanelConfig) { + async onInitFromDynamicConfig(config: PanelConfig) { if (config?.config?.columns) { this.columns = config.config.columns; } this.child = config.entity as Child; - this.loadData(this.child.getId()); + await this.loadData(this.child.getId()); } - loadData(id: string) { - this.childrenService - .getEducationalMaterialsOfChild(id) - .pipe(untilDestroyed(this)) - .subscribe((results) => { - this.records = results.sort( - (a, b) => - (b.date ? b.date.valueOf() : 0) - (a.date ? a.date.valueOf() : 0) - ); - this.updateSummary(); - }); + /** + * Loads the data for a given child and updates the summary + * @param id The id of the child to load the data for + */ + async loadData(id: string) { + this.records = await this.childrenService.getEducationalMaterialsOfChild( + id + ); + this.updateSummary(); } - generateNewRecordFactory() { - return () => { - const newAtt = new EducationalMaterial(Date.now().toString()); + newRecordFactory = () => { + const newAtt = new EducationalMaterial(Date.now().toString()); - // use last entered date as default, otherwise today's date - newAtt.date = this.records.length > 0 ? this.records[0].date : new Date(); - newAtt.child = this.child.getId(); + // use last entered date as default, otherwise today's date + newAtt.date = this.records.length > 0 ? this.records[0].date : new Date(); + newAtt.child = this.child.getId(); + newAtt.materialAmount = 1; - return newAtt; - }; - } + return newAtt; + }; + /** + * update the summary or generate a new one. + * The summary contains no duplicates and is in a + * human-readable format + */ updateSummary() { const summary = new Map(); this.records.forEach((m) => { @@ -75,11 +79,8 @@ export class EducationalMaterialComponent : 0; summary.set(m.materialType.label, previousValue + m.materialAmount); }); - - let summaryText = ""; - summary.forEach( - (v, k) => (summaryText = summaryText + k + ": " + v + ", ") - ); - this.summary = summaryText; + this.summary = [...summary] + .map(([key, value]) => key + ": " + value) + .join(", "); } } diff --git a/src/app/child-dev-project/educational-material/model/educational-material.ts b/src/app/child-dev-project/educational-material/model/educational-material.ts index 5a61f1d62e..9408ae83bf 100644 --- a/src/app/child-dev-project/educational-material/model/educational-material.ts +++ b/src/app/child-dev-project/educational-material/model/educational-material.ts @@ -22,6 +22,10 @@ import { ConfigurableEnumValue } from "../../../core/configurable-enum/configura @DatabaseEntity("EducationalMaterial") export class EducationalMaterial extends Entity { + static create(params: Partial): EducationalMaterial { + return Object.assign(new EducationalMaterial(), params); + } + @DatabaseField() child: string; // id of Child entity @DatabaseField({ label: $localize`:Date on which the material has been borrowed:Date`, @@ -31,10 +35,12 @@ export class EducationalMaterial extends Entity { label: $localize`:The material which has been borrowed:Material`, dataType: "configurable-enum", innerDataType: "materials", + required: true, }) materialType: ConfigurableEnumValue; @DatabaseField({ label: $localize`:The amount of the material which has been borrowed:Amount`, + required: true, }) materialAmount: number; @DatabaseField({ diff --git a/src/app/child-dev-project/health-checkup/health-checkup-component/health-checkup.component.spec.ts b/src/app/child-dev-project/health-checkup/health-checkup-component/health-checkup.component.spec.ts index 78b55d1fab..48c6bd75d5 100644 --- a/src/app/child-dev-project/health-checkup/health-checkup-component/health-checkup.component.spec.ts +++ b/src/app/child-dev-project/health-checkup/health-checkup-component/health-checkup.component.spec.ts @@ -24,9 +24,7 @@ describe("HealthCheckupComponent", () => { "getHealthChecksOfChild", ]); mockChildrenService.getChild.and.returnValue(of(child)); - mockChildrenService.getEducationalMaterialsOfChild.and.returnValue( - of([]) - ); + mockChildrenService.getEducationalMaterialsOfChild.and.resolveTo([]); mockChildrenService.getHealthChecksOfChild.and.returnValue(of([])); TestBed.configureTestingModule({ diff --git a/src/app/child-dev-project/notes/dashboard-widgets/no-recent-notes-dashboard/no-recent-notes-dashboard.component.html b/src/app/child-dev-project/notes/dashboard-widgets/no-recent-notes-dashboard/no-recent-notes-dashboard.component.html index c2249962d9..e03dd9f6c6 100644 --- a/src/app/child-dev-project/notes/dashboard-widgets/no-recent-notes-dashboard/no-recent-notes-dashboard.component.html +++ b/src/app/child-dev-project/notes/dashboard-widgets/no-recent-notes-dashboard/no-recent-notes-dashboard.component.html @@ -1,7 +1,7 @@
- +
diff --git a/src/app/child-dev-project/notes/dashboard-widgets/no-recent-notes-dashboard/no-recent-notes-dashboard.component.spec.ts b/src/app/child-dev-project/notes/dashboard-widgets/no-recent-notes-dashboard/no-recent-notes-dashboard.component.spec.ts index 5adb5c1ea8..1d0368aaef 100644 --- a/src/app/child-dev-project/notes/dashboard-widgets/no-recent-notes-dashboard/no-recent-notes-dashboard.component.spec.ts +++ b/src/app/child-dev-project/notes/dashboard-widgets/no-recent-notes-dashboard/no-recent-notes-dashboard.component.spec.ts @@ -12,6 +12,7 @@ import { RouterTestingModule } from "@angular/router/testing"; import { NoRecentNotesDashboardComponent } from "./no-recent-notes-dashboard.component"; import { ChildrenModule } from "../../../children/children.module"; import { Angulartics2Module } from "angulartics2"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("NoRecentNotesDashboardComponent", () => { let component: NoRecentNotesDashboardComponent; @@ -34,6 +35,7 @@ describe("NoRecentNotesDashboardComponent", () => { RouterTestingModule.withRoutes([]), EntityModule, Angulartics2Module.forRoot(), + FontAwesomeTestingModule, ], providers: [ { provide: ChildrenService, useValue: mockChildrenService }, diff --git a/src/app/child-dev-project/notes/dashboard-widgets/recent-notes-dashboard/recent-notes-dashboard.component.html b/src/app/child-dev-project/notes/dashboard-widgets/recent-notes-dashboard/recent-notes-dashboard.component.html index c9b94765f6..2703c1c22e 100644 --- a/src/app/child-dev-project/notes/dashboard-widgets/recent-notes-dashboard/recent-notes-dashboard.component.html +++ b/src/app/child-dev-project/notes/dashboard-widgets/recent-notes-dashboard/recent-notes-dashboard.component.html @@ -1,7 +1,7 @@
- +
@@ -34,7 +34,7 @@ [angularticsLabel]="'children-with-recent-report-count-' + count" i18n="Go to Notes Link|Link that will take the user to a list of notes" > - Go to Notes + Go to Notes
diff --git a/src/app/child-dev-project/notes/dashboard-widgets/recent-notes-dashboard/recent-notes-dashboard.component.spec.ts b/src/app/child-dev-project/notes/dashboard-widgets/recent-notes-dashboard/recent-notes-dashboard.component.spec.ts index e324214664..f07970c5e1 100644 --- a/src/app/child-dev-project/notes/dashboard-widgets/recent-notes-dashboard/recent-notes-dashboard.component.spec.ts +++ b/src/app/child-dev-project/notes/dashboard-widgets/recent-notes-dashboard/recent-notes-dashboard.component.spec.ts @@ -12,6 +12,7 @@ import { RouterTestingModule } from "@angular/router/testing"; import { RecentNotesDashboardComponent } from "./recent-notes-dashboard.component"; import { ChildrenModule } from "../../../children/children.module"; import { Angulartics2Module } from "angulartics2"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("RecentNotesDashboardComponent", () => { let component: RecentNotesDashboardComponent; @@ -34,6 +35,7 @@ describe("RecentNotesDashboardComponent", () => { RouterTestingModule.withRoutes([]), EntityModule, Angulartics2Module.forRoot(), + FontAwesomeTestingModule, ], providers: [ { provide: ChildrenService, useValue: mockChildrenService }, diff --git a/src/app/child-dev-project/notes/note-details/child-meeting-attendance/child-meeting-note-attendance.component.html b/src/app/child-dev-project/notes/note-details/child-meeting-attendance/child-meeting-note-attendance.component.html index 149a1eb0a1..52f861a99d 100644 --- a/src/app/child-dev-project/notes/note-details/child-meeting-attendance/child-meeting-note-attendance.component.html +++ b/src/app/child-dev-project/notes/note-details/child-meeting-attendance/child-meeting-note-attendance.component.html @@ -1,7 +1,7 @@
diff --git a/src/app/child-dev-project/notes/note-details/note-details.component.spec.ts b/src/app/child-dev-project/notes/note-details/note-details.component.spec.ts index 73bea300ff..a317dcf109 100644 --- a/src/app/child-dev-project/notes/note-details/note-details.component.spec.ts +++ b/src/app/child-dev-project/notes/note-details/note-details.component.spec.ts @@ -11,6 +11,7 @@ import { Angulartics2Module } from "angulartics2"; import { MatDialogRef } from "@angular/material/dialog"; import { defaultAttendanceStatusTypes } from "../../../core/config/default-config/default-attendance-status-types"; import { MockSessionModule } from "../../../core/session/mock-session.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; function generateTestNote(forChildren: Child[]) { const testNote = Note.create(new Date(), "test note"); @@ -57,6 +58,7 @@ describe("NoteDetailsComponent", () => { MatNativeDateModule, Angulartics2Module.forRoot(), MockSessionModule.withState(), + FontAwesomeTestingModule, ], providers: [ { provide: MatDialogRef, useValue: dialogRefMock }, diff --git a/src/app/child-dev-project/notes/notes-manager/notes-manager.component.spec.ts b/src/app/child-dev-project/notes/notes-manager/notes-manager.component.spec.ts index ec2d43d1d7..4cebd6a847 100644 --- a/src/app/child-dev-project/notes/notes-manager/notes-manager.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-manager/notes-manager.component.spec.ts @@ -31,6 +31,7 @@ import { BehaviorSubject } from "rxjs"; import { UpdatedEntity } from "../../../core/entity/model/entity-update"; import { ExportService } from "../../../core/export/export-service/export.service"; import { MockSessionModule } from "../../../core/session/mock-session.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("NotesManagerComponent", () => { let component: NotesManagerComponent; @@ -106,6 +107,7 @@ describe("NotesManagerComponent", () => { RouterTestingModule, Angulartics2Module.forRoot(), MockSessionModule.withState(), + FontAwesomeTestingModule, ], providers: [ { provide: FormDialogService, useValue: dialogMock }, diff --git a/src/app/child-dev-project/notes/notes.module.ts b/src/app/child-dev-project/notes/notes.module.ts index a8bac70b3d..e49f7c33f3 100644 --- a/src/app/child-dev-project/notes/notes.module.ts +++ b/src/app/child-dev-project/notes/notes.module.ts @@ -5,7 +5,6 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { MatDialogModule } from "@angular/material/dialog"; import { MatSelectModule } from "@angular/material/select"; import { NotesManagerComponent } from "./notes-manager/notes-manager.component"; -import { MatIconModule } from "@angular/material/icon"; import { MatExpansionModule } from "@angular/material/expansion"; import { MatButtonToggleModule } from "@angular/material/button-toggle"; import { MatTableModule } from "@angular/material/table"; @@ -43,6 +42,7 @@ import { MatSlideToggleModule } from "@angular/material/slide-toggle"; import { ChildMeetingNoteAttendanceComponent } from "./note-details/child-meeting-attendance/child-meeting-note-attendance.component"; import { EntityUtilsModule } from "../../core/entity-components/entity-utils/entity-utils.module"; import { NoteAttendanceCountBlockComponent } from "./note-attendance-block/note-attendance-count-block.component"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; @NgModule({ declarations: [ @@ -68,7 +68,6 @@ import { NoteAttendanceCountBlockComponent } from "./note-attendance-block/note- MatSidenavModule, MatButtonModule, MatButtonToggleModule, - MatIconModule, MatCardModule, MatSnackBarModule, MatDialogModule, @@ -97,6 +96,7 @@ import { NoteAttendanceCountBlockComponent } from "./note-attendance-block/note- AttendanceModule, MatSlideToggleModule, EntityUtilsModule, + FontAwesomeModule, ], providers: [], entryComponents: [NoteDetailsComponent], diff --git a/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard-widget.module.ts b/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard-widget.module.ts index e7ea3e60fa..bfffc8400f 100644 --- a/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard-widget.module.ts +++ b/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard-widget.module.ts @@ -21,12 +21,12 @@ import { FlexLayoutModule } from "@angular/flex-layout"; import { MatButtonModule } from "@angular/material/button"; import { MatCardModule } from "@angular/material/card"; import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatIconModule } from "@angular/material/icon"; import { MatInputModule } from "@angular/material/input"; import { MatProgressBarModule } from "@angular/material/progress-bar"; import { ChildrenModule } from "../children/children.module"; import { ProgressDashboardComponent } from "./progress-dashboard/progress-dashboard.component"; import { FormsModule } from "@angular/forms"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; @NgModule({ imports: [ @@ -34,12 +34,12 @@ import { FormsModule } from "@angular/forms"; FormsModule, FlexLayoutModule, MatCardModule, - MatIconModule, MatButtonModule, MatProgressBarModule, MatInputModule, MatFormFieldModule, ChildrenModule, + FontAwesomeModule, ], declarations: [ProgressDashboardComponent], }) diff --git a/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html b/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html index 3e4b7ce9f8..7dc36e6581 100644 --- a/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html +++ b/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html @@ -1,7 +1,7 @@
- +
{{ data.getTotalPercentage() | percent: "1.0-0" @@ -27,13 +27,13 @@
@@ -105,7 +105,7 @@ diff --git a/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.spec.ts b/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.spec.ts index 2bd5917b91..745d2ee89a 100644 --- a/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.spec.ts +++ b/src/app/child-dev-project/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.spec.ts @@ -4,6 +4,7 @@ import { ProgressDashboardComponent } from "./progress-dashboard.component"; import { EntityMapperService } from "../../../core/entity/entity-mapper.service"; import { AlertService } from "../../../core/alerts/alert.service"; import { ProgressDashboardWidgetModule } from "../progress-dashboard-widget.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ProgressDashboardComponent", () => { let component: ProgressDashboardComponent; @@ -18,7 +19,7 @@ describe("ProgressDashboardComponent", () => { mockEntityService.load.and.resolveTo({ title: "test", parts: [] }); TestBed.configureTestingModule({ - imports: [ProgressDashboardWidgetModule], + imports: [ProgressDashboardWidgetModule, FontAwesomeTestingModule], providers: [ { provide: EntityMapperService, useValue: mockEntityService }, { diff --git a/src/app/child-dev-project/schools/children-overview/children-overview.component.spec.ts b/src/app/child-dev-project/schools/children-overview/children-overview.component.spec.ts index 054cdbe0fd..cee75aaefd 100644 --- a/src/app/child-dev-project/schools/children-overview/children-overview.component.spec.ts +++ b/src/app/child-dev-project/schools/children-overview/children-overview.component.spec.ts @@ -15,6 +15,7 @@ import { Router } from "@angular/router"; import { MockSessionModule } from "../../../core/session/mock-session.module"; import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; import { ChildrenService } from "../../children/children.service"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ChildrenOverviewComponent", () => { let component: ChildrenOverviewComponent; @@ -32,6 +33,7 @@ describe("ChildrenOverviewComponent", () => { RouterTestingModule, NoopAnimationsModule, MockSessionModule.withState(), + FontAwesomeTestingModule, ], providers: [ { provide: ChildrenService, useValue: mockChildrenService }, diff --git a/src/app/child-dev-project/schools/school-block/school-block.component.html b/src/app/child-dev-project/schools/school-block/school-block.component.html index 17e267adfb..f201ad2e44 100644 --- a/src/app/child-dev-project/schools/school-block/school-block.component.html +++ b/src/app/child-dev-project/schools/school-block/school-block.component.html @@ -1,5 +1,5 @@ - + {{ entity?.name }} diff --git a/src/app/child-dev-project/schools/school-block/school-block.component.spec.ts b/src/app/child-dev-project/schools/school-block/school-block.component.spec.ts index c6078f0434..071d2326b7 100644 --- a/src/app/child-dev-project/schools/school-block/school-block.component.spec.ts +++ b/src/app/child-dev-project/schools/school-block/school-block.component.spec.ts @@ -2,10 +2,11 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { SchoolBlockComponent } from "./school-block.component"; import { RouterTestingModule } from "@angular/router/testing"; -import { MatIconModule } from "@angular/material/icon"; import { School } from "../model/school"; import { EntityMapperService } from "../../../core/entity/entity-mapper.service"; import { ConfigService } from "../../../core/config/config.service"; +import { FaDynamicIconComponent } from "../../../core/view/fa-dynamic-icon/fa-dynamic-icon.component"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("SchoolBlockComponent", () => { let component: SchoolBlockComponent; @@ -19,8 +20,8 @@ describe("SchoolBlockComponent", () => { mockConfigService = jasmine.createSpyObj(["getConfig"]); TestBed.configureTestingModule({ - declarations: [SchoolBlockComponent], - imports: [RouterTestingModule, MatIconModule], + declarations: [SchoolBlockComponent, FaDynamicIconComponent], + imports: [RouterTestingModule, FontAwesomeTestingModule], providers: [ { provide: EntityMapperService, useValue: mockEntityMapper }, { provide: ConfigService, useValue: mockConfigService }, @@ -33,6 +34,7 @@ describe("SchoolBlockComponent", () => { fixture = TestBed.createComponent(SchoolBlockComponent); component = fixture.componentInstance; component.entity = new School(""); + component.icon = "university"; fixture.detectChanges(); }); diff --git a/src/app/child-dev-project/schools/school-block/school-block.component.ts b/src/app/child-dev-project/schools/school-block/school-block.component.ts index 124dfcf244..542449053f 100644 --- a/src/app/child-dev-project/schools/school-block/school-block.component.ts +++ b/src/app/child-dev-project/schools/school-block/school-block.component.ts @@ -3,7 +3,6 @@ import { HostListener, Input, OnChanges, - OnInit, SimpleChanges, } from "@angular/core"; import { Router } from "@angular/router"; @@ -18,9 +17,8 @@ import { ViewConfig } from "../../../core/view/dynamic-routing/view-config.inter templateUrl: "./school-block.component.html", styleUrls: ["./school-block.component.scss"], }) -export class SchoolBlockComponent - implements OnInitDynamicComponent, OnChanges, OnInit { - iconName: String; +export class SchoolBlockComponent implements OnInitDynamicComponent, OnChanges { + icon: string; @Input() entity: School = new School(""); @Input() entityId: string; @Input() linkDisabled: boolean; @@ -31,12 +29,10 @@ export class SchoolBlockComponent private router: Router, private entityMapper: EntityMapperService, private configService: ConfigService - ) {} - - ngOnInit() { - this.iconName = - "fa-" + - this.configService.getConfig("view:school/:id")?.config?.icon; + ) { + this.icon = this.configService.getConfig( + "view:school/:id" + )?.config?.icon; } ngOnChanges(changes: SimpleChanges) { diff --git a/src/app/child-dev-project/schools/schools-list/schools-list.component.spec.ts b/src/app/child-dev-project/schools/schools-list/schools-list.component.spec.ts index 95be95a110..faa42a1754 100644 --- a/src/app/child-dev-project/schools/schools-list/schools-list.component.spec.ts +++ b/src/app/child-dev-project/schools/schools-list/schools-list.component.spec.ts @@ -17,6 +17,7 @@ import { EntityListConfig } from "../../../core/entity-components/entity-list/En import { ExportService } from "../../../core/export/export-service/export.service"; import { MockSessionModule } from "../../../core/session/mock-session.module"; import { EntityMapperService } from "../../../core/entity/entity-mapper.service"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("SchoolsListComponent", () => { let component: SchoolsListComponent; @@ -53,6 +54,7 @@ describe("SchoolsListComponent", () => { Angulartics2Module.forRoot(), NoopAnimationsModule, MockSessionModule.withState(), + FontAwesomeTestingModule, ], providers: [ { provide: ActivatedRoute, useValue: routeMock }, diff --git a/src/app/child-dev-project/schools/schools.module.ts b/src/app/child-dev-project/schools/schools.module.ts index f63563c95a..5a2e895222 100644 --- a/src/app/child-dev-project/schools/schools.module.ts +++ b/src/app/child-dev-project/schools/schools.module.ts @@ -9,7 +9,6 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; import { MatDialogModule } from "@angular/material/dialog"; import { MatExpansionModule } from "@angular/material/expansion"; import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatIconModule } from "@angular/material/icon"; import { MatInputModule } from "@angular/material/input"; import { MatSidenavModule } from "@angular/material/sidenav"; import { MatSnackBarModule } from "@angular/material/snack-bar"; @@ -27,6 +26,8 @@ import { Angulartics2Module } from "angulartics2"; import { ChildrenOverviewComponent } from "./children-overview/children-overview.component"; import { EntityListModule } from "../../core/entity-components/entity-list/entity-list.module"; import { EntitySubrecordModule } from "../../core/entity-components/entity-subrecord/entity-subrecord.module"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { ViewModule } from "../../core/view/view.module"; @NgModule({ imports: [ @@ -39,7 +40,6 @@ import { EntitySubrecordModule } from "../../core/entity-components/entity-subre MatButtonModule, FlexLayoutModule, MatSnackBarModule, - MatIconModule, MatCheckboxModule, ReactiveFormsModule, MatInputModule, @@ -59,7 +59,6 @@ import { EntitySubrecordModule } from "../../core/entity-components/entity-subre MatSidenavModule, MatButtonModule, MatButtonToggleModule, - MatIconModule, MatCardModule, MatSnackBarModule, MatDialogModule, @@ -69,6 +68,8 @@ import { EntitySubrecordModule } from "../../core/entity-components/entity-subre Angulartics2Module, EntityListModule, EntitySubrecordModule, + FontAwesomeModule, + ViewModule, ], declarations: [ SchoolBlockComponent, diff --git a/src/app/conflict-resolution/conflict-resolution.module.ts b/src/app/conflict-resolution/conflict-resolution.module.ts index 0049004b34..bf95bef265 100644 --- a/src/app/conflict-resolution/conflict-resolution.module.ts +++ b/src/app/conflict-resolution/conflict-resolution.module.ts @@ -3,7 +3,6 @@ import { CommonModule } from "@angular/common"; import { ConflictResolutionListComponent } from "./conflict-resolution-list/conflict-resolution-list.component"; import { MatTableModule } from "@angular/material/table"; import { MatSortModule } from "@angular/material/sort"; -import { MatIconModule } from "@angular/material/icon"; import { MatButtonModule } from "@angular/material/button"; import { MatPaginatorModule } from "@angular/material/paginator"; import { MatExpansionModule } from "@angular/material/expansion"; @@ -43,7 +42,6 @@ routes: Routes = [ CommonModule, MatTableModule, MatSortModule, - MatIconModule, MatButtonModule, MatPaginatorModule, MatExpansionModule, diff --git a/src/app/core/admin/admin.module.ts b/src/app/core/admin/admin.module.ts index 03612692ef..2bb2153acc 100644 --- a/src/app/core/admin/admin.module.ts +++ b/src/app/core/admin/admin.module.ts @@ -14,10 +14,11 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { MatDialogModule } from "@angular/material/dialog"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; -import { MatIconModule } from "@angular/material/icon"; import { UserListComponent } from "./user-list/user-list.component"; import { BackupService } from "./services/backup.service"; import { MatTooltipModule } from "@angular/material/tooltip"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { DataImportModule } from "../../features/data-import/data-import.module"; /** * GUI for administrative users to manage and maintain background and technical aspects of the app. @@ -38,8 +39,9 @@ import { MatTooltipModule } from "@angular/material/tooltip"; ReactiveFormsModule, MatFormFieldModule, MatInputModule, - MatIconModule, MatTooltipModule, + FontAwesomeModule, + DataImportModule, ], declarations: [AdminComponent, UserListComponent], providers: [ChildPhotoUpdateService, BackupService], diff --git a/src/app/core/admin/admin/admin.component.html b/src/app/core/admin/admin/admin.component.html index a5e51a6403..fae1db1ac8 100644 --- a/src/app/core/admin/admin/admin.component.html +++ b/src/app/core/admin/admin/admin.component.html @@ -87,19 +87,7 @@

Export

Download whole database (.csv)   - - +


diff --git a/src/app/core/admin/admin/admin.component.spec.ts b/src/app/core/admin/admin/admin.component.spec.ts index ec87335b87..56e31ccfa3 100644 --- a/src/app/core/admin/admin/admin.component.spec.ts +++ b/src/app/core/admin/admin/admin.component.spec.ts @@ -35,13 +35,7 @@ describe("AdminComponent", () => { ); const mockBackupService: jasmine.SpyObj = jasmine.createSpyObj( BackupService, - [ - "getJsonExport", - "getCsvExport", - "clearDatabase", - "importJson", - "importCsv", - ] + ["getJsonExport", "getCsvExport", "clearDatabase", "importJson"] ); const confirmationDialogMock: jasmine.SpyObj = jasmine.createSpyObj( @@ -189,20 +183,6 @@ describe("AdminComponent", () => { expect(mockBackupService.importJson).toHaveBeenCalled(); })); - it("should open dialog and call backup service when loading csv", fakeAsync(() => { - const mockFileReader = createFileReaderMock(); - mockBackupService.getJsonExport.and.returnValue(Promise.resolve(null)); - createDialogMock(); - - component.loadCsv(null); - expect(mockBackupService.getJsonExport).toHaveBeenCalled(); - tick(); - expect(mockFileReader.readAsText).toHaveBeenCalled(); - expect(confirmationDialogMock.openDialog).toHaveBeenCalled(); - flush(); - expect(mockBackupService.importCsv).toHaveBeenCalled(); - })); - it("should open dialog when clearing database", fakeAsync(() => { mockBackupService.getJsonExport.and.returnValue(Promise.resolve("")); createDialogMock(); diff --git a/src/app/core/admin/admin/admin.component.ts b/src/app/core/admin/admin/admin.component.ts index 219fa50b27..a41631c6a2 100644 --- a/src/app/core/admin/admin/admin.component.ts +++ b/src/app/core/admin/admin/admin.component.ts @@ -15,6 +15,7 @@ import { ChildrenMigrationService } from "../../../child-dev-project/children/ch import { ConfigMigrationService } from "../../config/config-migration.service"; import { PermissionsMigrationService } from "../../permissions/permissions-migration.service"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; +import { readFile } from "../../../utils/utils"; /** * Admin GUI giving administrative users different options/actions. @@ -97,7 +98,7 @@ export class AdminComponent implements OnInit { } async uploadConfigFile(file: Blob) { - const loadedFile = await this.readFile(file); + const loadedFile = await readFile(file); await this.configService.saveConfig( this.entityMapper, JSON.parse(loadedFile) @@ -113,23 +114,13 @@ export class AdminComponent implements OnInit { tempLink.click(); } - private readFile(file: Blob): Promise { - return new Promise((resolve) => { - const fileReader = new FileReader(); - fileReader.addEventListener("load", () => - resolve(fileReader.result as string) - ); - fileReader.readAsText(file); - }); - } - /** * Reset the database to the state from the loaded backup file. * @param file The file object of the backup to be restored */ async loadBackup(file) { const restorePoint = await this.backupService.getJsonExport(); - const newData = await this.readFile(file); + const newData = await readFile(file); const dialogRef = this.confirmationDialog.openDialog( $localize`Overwrite complete database?`, @@ -163,45 +154,6 @@ export class AdminComponent implements OnInit { }); } - /** - * Add the data from the loaded file to the database, inserting and updating records. - * @param file The file object of the csv data to be loaded - */ - async loadCsv(file: Blob) { - const restorePoint = await this.backupService.getJsonExport(); - const newData = await this.readFile(file); - - const dialogRef = this.confirmationDialog.openDialog( - $localize`Import new data?`, - $localize`Are you sure you want to import this file? This will add or update ${ - newData.trim().split("\n").length - 1 - } records from the loaded file. Existing records with same "_id" in the database will be overwritten!` - ); - - dialogRef.afterClosed().subscribe(async (confirmed) => { - if (!confirmed) { - return; - } - - await this.backupService.importCsv(newData, true); - - const snackBarRef = this.snackBar.open( - $localize`Import completed?`, - "Undo", - { - duration: 8000, - } - ); - snackBarRef - .onAction() - .pipe(untilDestroyed(this)) - .subscribe(async () => { - await this.backupService.clearDatabase(); - await this.backupService.importJson(restorePoint, true); - }); - }); - } - /** * Reset the database removing all entities except user accounts. */ diff --git a/src/app/core/admin/user-list/user-list.component.html b/src/app/core/admin/user-list/user-list.component.html index fe3e97e7af..817453beb7 100644 --- a/src/app/core/admin/user-list/user-list.component.html +++ b/src/app/core/admin/user-list/user-list.component.html @@ -12,10 +12,10 @@ Details - + > diff --git a/src/app/core/alerts/alerts.module.ts b/src/app/core/alerts/alerts.module.ts index 52cb33e22d..4eb9cf7b6f 100644 --- a/src/app/core/alerts/alerts.module.ts +++ b/src/app/core/alerts/alerts.module.ts @@ -22,8 +22,8 @@ import { AlertComponent } from "./alerts/alert.component"; import { AlertService } from "./alert.service"; import { MatButtonModule } from "@angular/material/button"; import { MatCardModule } from "@angular/material/card"; -import { MatIconModule } from "@angular/material/icon"; import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; /** * Show alert message to the user informing about events or errors. @@ -34,7 +34,7 @@ import { MatSnackBarModule } from "@angular/material/snack-bar"; MatSnackBarModule, MatCardModule, MatButtonModule, - MatIconModule, + FontAwesomeModule, ], declarations: [AlertComponent], exports: [AlertComponent], diff --git a/src/app/core/alerts/alerts/alert.component.html b/src/app/core/alerts/alerts/alert.component.html index f6cf818c90..53ac476638 100644 --- a/src/app/core/alerts/alerts/alert.component.html +++ b/src/app/core/alerts/alerts/alert.component.html @@ -21,10 +21,10 @@ (click)="alert.notificationRef.dismiss()" class="ndb-alert-dismiss" > - + icon="window-close" + >

{{ alert.message }}

diff --git a/src/app/core/alerts/alerts/alert.component.spec.ts b/src/app/core/alerts/alerts/alert.component.spec.ts index 9469adf4b0..8356da0329 100644 --- a/src/app/core/alerts/alerts/alert.component.spec.ts +++ b/src/app/core/alerts/alerts/alert.component.spec.ts @@ -20,10 +20,10 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { AlertComponent } from "./alert.component"; import { AlertService } from "../alert.service"; import { MatButtonModule } from "@angular/material/button"; -import { MatIconModule } from "@angular/material/icon"; import { MAT_SNACK_BAR_DATA } from "@angular/material/snack-bar"; import { Alert } from "../alert"; import { AlertDisplay } from "../alert-display"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("AlertComponent", () => { let component: AlertComponent; @@ -33,6 +33,7 @@ describe("AlertComponent", () => { waitForAsync(() => { TestBed.configureTestingModule({ declarations: [AlertComponent], + imports: [FontAwesomeTestingModule, MatButtonModule], providers: [ AlertService, { @@ -40,7 +41,6 @@ describe("AlertComponent", () => { useValue: new Alert("test", Alert.WARNING, AlertDisplay.PERSISTENT), }, ], - imports: [MatIconModule, MatButtonModule], }).compileComponents(); }) ); diff --git a/src/app/core/analytics/analytics.service.spec.ts b/src/app/core/analytics/analytics.service.spec.ts index b1bc05d7b7..4d0d839067 100644 --- a/src/app/core/analytics/analytics.service.spec.ts +++ b/src/app/core/analytics/analytics.service.spec.ts @@ -40,6 +40,9 @@ describe("AnalyticsService", () => { ], }); service = TestBed.inject(AnalyticsService); + + // make _paq a array + window["_paq"] = []; }); it("should be created", () => { diff --git a/src/app/core/analytics/analytics.service.ts b/src/app/core/analytics/analytics.service.ts index 67e92a72ab..547f681163 100644 --- a/src/app/core/analytics/analytics.service.ts +++ b/src/app/core/analytics/analytics.service.ts @@ -109,19 +109,17 @@ export class AnalyticsService { } window["_paq"].push(["trackPageView"]); window["_paq"].push(["enableLinkTracking"]); - (() => { - const u = url.endsWith("/") ? url : url + "/"; - window["_paq"].push(["setTrackerUrl", u + "matomo.php"]); - window["_paq"].push(["setSiteId", id]); - const d = document; - const g = d.createElement("script"); - const s = d.getElementsByTagName("script")[0]; - g.type = "text/javascript"; - g.async = true; - g.defer = true; - g.src = u + "matomo.js"; - s.parentNode.insertBefore(g, s); - })(); + const u = url.endsWith("/") ? url : url + "/"; + window["_paq"].push(["setTrackerUrl", u + "matomo.php"]); + window["_paq"].push(["setSiteId", id]); + const d = document; + const g = d.createElement("script"); + const s = d.getElementsByTagName("script")[0]; + g.type = "text/javascript"; + g.async = true; + g.defer = true; + g.src = u + "matomo.js"; + s.parentNode.insertBefore(g, s); } /** diff --git a/src/app/core/coming-soon/coming-soon.module.ts b/src/app/core/coming-soon/coming-soon.module.ts index 76fe1e5cf3..1bec71fd93 100644 --- a/src/app/core/coming-soon/coming-soon.module.ts +++ b/src/app/core/coming-soon/coming-soon.module.ts @@ -3,8 +3,8 @@ import { CommonModule } from "@angular/common"; import { ComingSoonComponent } from "./coming-soon/coming-soon.component"; import { MatButtonModule } from "@angular/material/button"; import { FlexModule } from "@angular/flex-layout"; -import { MatIconModule } from "@angular/material/icon"; import { RouterModule } from "@angular/router"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; /** * Provides a generic page to announce features that are not implemented yet including tracking user interest to analytics. @@ -31,13 +31,13 @@ import { RouterModule } from "@angular/router"; CommonModule, MatButtonModule, FlexModule, - MatIconModule, RouterModule.forChild([ { path: ":feature", component: ComingSoonComponent, }, ]), + FontAwesomeModule, ], exports: [RouterModule], }) diff --git a/src/app/core/coming-soon/coming-soon/coming-soon.component.html b/src/app/core/coming-soon/coming-soon/coming-soon.component.html index 6d651f8da0..32b38c7866 100644 --- a/src/app/core/coming-soon/coming-soon/coming-soon.component.html +++ b/src/app/core/coming-soon/coming-soon/coming-soon.component.html @@ -24,12 +24,12 @@

Coming Soon

a feature-request " > - + > I need this feature
diff --git a/src/app/core/coming-soon/coming-soon/coming-soon.component.spec.ts b/src/app/core/coming-soon/coming-soon/coming-soon.component.spec.ts index 4a55f7e880..54d6f78900 100644 --- a/src/app/core/coming-soon/coming-soon/coming-soon.component.spec.ts +++ b/src/app/core/coming-soon/coming-soon/coming-soon.component.spec.ts @@ -5,6 +5,8 @@ import { BehaviorSubject } from "rxjs"; import { AlertService } from "../../alerts/alert.service"; import { ActivatedRoute, convertToParamMap } from "@angular/router"; import { AnalyticsService } from "../../analytics/analytics.service"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ComingSoonComponent", () => { let component: ComingSoonComponent; @@ -27,6 +29,7 @@ describe("ComingSoonComponent", () => { TestBed.configureTestingModule({ declarations: [ComingSoonComponent], + imports: [FontAwesomeModule, FontAwesomeTestingModule], providers: [ { provide: AnalyticsService, useValue: mockAnalytics }, { provide: AlertService, useValue: { addInfo: () => {} } }, diff --git a/src/app/core/coming-soon/coming-soon/coming-soon.stories.ts b/src/app/core/coming-soon/coming-soon/coming-soon.stories.ts index 0347cf7c29..98d783b4cb 100644 --- a/src/app/core/coming-soon/coming-soon/coming-soon.stories.ts +++ b/src/app/core/coming-soon/coming-soon/coming-soon.stories.ts @@ -1,6 +1,5 @@ import { Story, Meta } from "@storybook/angular/types-6-0"; import { moduleMetadata } from "@storybook/angular"; -import { FontAwesomeIconsModule } from "../../icons/font-awesome-icons.module"; import { ComingSoonComponent } from "./coming-soon.component"; import { ComingSoonModule } from "../coming-soon.module"; import { AlertsModule } from "../../alerts/alerts.module"; @@ -17,7 +16,6 @@ export default { imports: [ ComingSoonModule, AlertsModule, - FontAwesomeIconsModule, BrowserAnimationsModule, RouterTestingModule, Angulartics2RouterlessModule.forRoot(), diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts index 708e74f5c3..a403182eff 100644 --- a/src/app/core/config/config-fix.ts +++ b/src/app/core/config/config-fix.ts @@ -40,12 +40,12 @@ export const defaultJsonConfig = { }, { "name": $localize`:Menu item:Recurring Activities`, - "icon": "calendar", + "icon": "calendar-alt", "link": "/recurring-activity" }, { "name": $localize`:Menu item|Record attendance menu item:Record Attendance`, - "icon": "calendar-check-o", + "icon": "calendar-check", "link": "/attendance/add/day" }, { @@ -55,7 +55,7 @@ export const defaultJsonConfig = { }, { "name": $localize`:Menu item:Notes`, - "icon": "file-text", + "icon": "file-alt", "link": "/note" }, { @@ -80,7 +80,7 @@ export const defaultJsonConfig = { }, { "name": $localize`:Menu item:Help`, - "icon": "question-circle", + "icon": "question", "link": "/help" } ] @@ -150,7 +150,7 @@ export const defaultJsonConfig = { "shortcuts": [ { "label": $localize`:Dashboard shortcut widget|record attendance shortcut:Record Attendance`, - "icon": "calendar-check-o", + "icon": "calendar-check", "link": "/attendance/add/day", } ] @@ -706,7 +706,7 @@ export const defaultJsonConfig = { ] } ], - "icon": "calendar" + "icon": "calendar-alt" } }, "view:report": { diff --git a/src/app/core/configurable-enum/configurable-enum.module.ts b/src/app/core/configurable-enum/configurable-enum.module.ts index 97ad8db49a..d10a97dfd3 100644 --- a/src/app/core/configurable-enum/configurable-enum.module.ts +++ b/src/app/core/configurable-enum/configurable-enum.module.ts @@ -9,7 +9,6 @@ import { DisplayConfigurableEnumComponent } from "./display-configurable-enum/di import { MatFormFieldModule } from "@angular/material/form-field"; import { MatSelectModule } from "@angular/material/select"; import { ReactiveFormsModule } from "@angular/forms"; -import { MatIconModule } from "@angular/material/icon"; import { MatTooltipModule } from "@angular/material/tooltip"; /** @@ -64,7 +63,6 @@ import { MatTooltipModule } from "@angular/material/tooltip"; MatFormFieldModule, MatSelectModule, ReactiveFormsModule, - MatIconModule, MatTooltipModule, ], exports: [ConfigurableEnumDirective], diff --git a/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.html b/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.html index b208f491af..b50c006424 100644 --- a/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.html +++ b/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.html @@ -7,5 +7,8 @@ {{ o.label }} + + This field is required + diff --git a/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.spec.ts b/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.spec.ts index 38b761ea49..3c1760e04e 100644 --- a/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.spec.ts +++ b/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.spec.ts @@ -2,9 +2,12 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditConfigurableEnumComponent } from "./edit-configurable-enum.component"; import { EntityDetailsModule } from "../../entity-components/entity-details/entity-details.module"; -import { FormControl, FormGroup } from "@angular/forms"; +import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { ConfigService } from "../../config/config.service"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatSelectModule } from "@angular/material/select"; +import { ConfigurableEnumModule } from "../configurable-enum.module"; describe("EditConfigurableEnumComponent", () => { let component: EditConfigurableEnumComponent; @@ -16,7 +19,14 @@ describe("EditConfigurableEnumComponent", () => { mockConfigService = jasmine.createSpyObj(["getConfig"]); mockConfigService.getConfig.and.returnValue([]); await TestBed.configureTestingModule({ - imports: [EntityDetailsModule, NoopAnimationsModule], + imports: [ + EntityDetailsModule, + NoopAnimationsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatSelectModule, + ConfigurableEnumModule, + ], declarations: [EditConfigurableEnumComponent], providers: [{ provide: ConfigService, useValue: mockConfigService }], }).compileComponents(); diff --git a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget.module.ts b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget.module.ts index f62a62ed9e..711579ee2f 100644 --- a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget.module.ts +++ b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget.module.ts @@ -2,18 +2,20 @@ import { NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; import { DashboardShortcutWidgetComponent } from "./dashboard-shortcut-widget/dashboard-shortcut-widget.component"; import { MatCardModule } from "@angular/material/card"; -import { MatIconModule } from "@angular/material/icon"; import { RouterModule } from "@angular/router"; import { Angulartics2Module } from "angulartics2"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { ViewModule } from "../view/view.module"; @NgModule({ declarations: [DashboardShortcutWidgetComponent], imports: [ CommonModule, MatCardModule, - MatIconModule, RouterModule, + FontAwesomeModule, Angulartics2Module, + ViewModule, ], exports: [DashboardShortcutWidgetComponent], }) diff --git a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.html b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.html index 79453db24b..f7cf07b097 100644 --- a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.html +++ b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.html @@ -1,7 +1,7 @@
- +
- + {{ entry.label }} diff --git a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.spec.ts b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.spec.ts index afb844f52a..aa012bd5fd 100644 --- a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.spec.ts +++ b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { DashboardShortcutWidgetComponent } from "./dashboard-shortcut-widget.component"; import { DashboardShortcutWidgetModule } from "../dashboard-shortcut-widget.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ShortcutDashboardWidgetComponent", () => { let component: DashboardShortcutWidgetComponent; @@ -8,7 +9,7 @@ describe("ShortcutDashboardWidgetComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DashboardShortcutWidgetModule], + imports: [DashboardShortcutWidgetModule, FontAwesomeTestingModule], }).compileComponents(); }); diff --git a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.ts b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.ts index 3d98d2502d..f5cba7d578 100644 --- a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.ts +++ b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { MenuItem } from "../../navigation/menu-item"; import { OnInitDynamicComponent } from "../../view/dynamic-components/on-init-dynamic-component.interface"; @@ -11,14 +11,10 @@ import { OnInitDynamicComponent } from "../../view/dynamic-components/on-init-dy styleUrls: ["./dashboard-shortcut-widget.component.scss"], }) export class DashboardShortcutWidgetComponent - implements OnInit, OnInitDynamicComponent { + implements OnInitDynamicComponent { /** displayed entries, each representing one line displayed as a shortcut */ @Input() shortcuts: MenuItem[] = []; - constructor() {} - - ngOnInit(): void {} - onInitFromDynamicConfig(config: any) { this.shortcuts = config.shortcuts; } diff --git a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.stories.ts b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.stories.ts index 65d4aeaa43..0e198f3329 100644 --- a/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.stories.ts +++ b/src/app/core/dashboard-shortcut-widget/dashboard-shortcut-widget/dashboard-shortcut-widget.stories.ts @@ -2,7 +2,6 @@ import { Story, Meta } from "@storybook/angular/types-6-0"; import { moduleMetadata } from "@storybook/angular"; import { RouterTestingModule } from "@angular/router/testing"; import { Angulartics2Module } from "angulartics2"; -import { FontAwesomeIconsModule } from "../../icons/font-awesome-icons.module"; import { DashboardShortcutWidgetModule } from "../dashboard-shortcut-widget.module"; import { DashboardShortcutWidgetComponent } from "./dashboard-shortcut-widget.component"; import { MenuItem } from "../../navigation/menu-item"; @@ -14,7 +13,6 @@ export default { moduleMetadata({ imports: [ DashboardShortcutWidgetModule, - FontAwesomeIconsModule, RouterTestingModule, Angulartics2Module.forRoot(), ], diff --git a/src/app/core/entity-components/entity-details/entity-details.component.html b/src/app/core/entity-components/entity-details/entity-details.component.html index b9d859466c..49cc7f2cb6 100644 --- a/src/app/core/entity-components/entity-details/entity-details.component.html +++ b/src/app/core/entity-components/entity-details/entity-details.component.html @@ -25,10 +25,10 @@

matTooltip="Back" style="vertical-align: middle" > - + - + {{ entity?.toString() }} (opened)="trackPanelOpen(panelConfig.title)" > - - + {{ panelConfig.title }}
diff --git a/src/app/core/entity-components/entity-details/entity-details.component.scss b/src/app/core/entity-components/entity-details/entity-details.component.scss index ef8a6ffda5..6271167d0a 100644 --- a/src/app/core/entity-components/entity-details/entity-details.component.scss +++ b/src/app/core/entity-components/entity-details/entity-details.component.scss @@ -30,3 +30,7 @@ .mat-expansion-panel-spacing { margin: 0 0; } + +.icon { + margin-right: 10px; +} diff --git a/src/app/core/entity-components/entity-details/entity-details.component.spec.ts b/src/app/core/entity-components/entity-details/entity-details.component.spec.ts index e8d97e5f23..664aa32efc 100644 --- a/src/app/core/entity-components/entity-details/entity-details.component.spec.ts +++ b/src/app/core/entity-components/entity-details/entity-details.component.spec.ts @@ -10,15 +10,18 @@ import { Observable, of, Subscriber } from "rxjs"; import { MatNativeDateModule } from "@angular/material/core"; import { ActivatedRoute, Router } from "@angular/router"; import { RouterTestingModule } from "@angular/router/testing"; -import { MatSnackBar } from "@angular/material/snack-bar"; import { EntityDetailsConfig, PanelConfig } from "./EntityDetailsConfig"; import { ChildrenModule } from "../../../child-dev-project/children/children.module"; import { Child } from "../../../child-dev-project/children/model/child"; -import { ConfirmationDialogService } from "../../confirmation-dialog/confirmation-dialog.service"; import { EntityPermissionsService } from "../../permissions/entity-permissions.service"; import { ChildrenService } from "../../../child-dev-project/children/children.service"; import { MockEntityMapperService } from "../../entity/mock-entity-mapper-service"; import { MockSessionModule } from "../../session/mock-session.module"; +import { + EntityRemoveService, + RemoveResult, +} from "../../entity/entity-remove.service"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("EntityDetailsComponent", () => { let component: EntityDetailsComponent; @@ -63,6 +66,7 @@ describe("EntityDetailsComponent", () => { let mockChildrenService: jasmine.SpyObj; let mockedEntityMapper: MockEntityMapperService; + let mockEntityRemoveService: jasmine.SpyObj; beforeEach( waitForAsync(() => { @@ -70,6 +74,7 @@ describe("EntityDetailsComponent", () => { "getSchoolRelationsFor", "getAserResultsOfChild", ]); + mockEntityRemoveService = jasmine.createSpyObj(["remove"]); mockChildrenService.getSchoolRelationsFor.and.resolveTo([]); mockChildrenService.getAserResultsOfChild.and.returnValue(of([])); TestBed.configureTestingModule({ @@ -78,6 +83,7 @@ describe("EntityDetailsComponent", () => { MatNativeDateModule, RouterTestingModule, MockSessionModule.withState(), + FontAwesomeTestingModule, ], providers: [ { provide: ActivatedRoute, useValue: mockedRoute }, @@ -86,6 +92,7 @@ describe("EntityDetailsComponent", () => { useValue: mockEntityPermissionsService, }, { provide: ChildrenService, useValue: mockChildrenService }, + { provide: EntityRemoveService, useValue: mockEntityRemoveService }, ], }).compileComponents(); mockedEntityMapper = TestBed.inject(MockEntityMapperService); @@ -133,30 +140,28 @@ describe("EntityDetailsComponent", () => { expect(component.entity).toBe(testChild); })); + it("should navigate back when deleting an entity", fakeAsync(() => { + const mockRemoveResult = of(RemoveResult.REMOVED); + mockEntityRemoveService.remove.and.returnValue(mockRemoveResult); + component.entity = new Child("Test-Child"); + spyOn(component, "navigateBack"); + + component.removeEntity(); + tick(); + + expect(component.navigateBack).toHaveBeenCalled(); + })); + it("should route back when deleting is undone", fakeAsync(() => { - const testChild = new Child("Test-Child"); - component.entity = testChild; - const dialogRef = fixture.debugElement.injector.get( - ConfirmationDialogService - ); - const snackBar = fixture.debugElement.injector.get(MatSnackBar); + const mockResult = of(RemoveResult.REMOVED, RemoveResult.UNDONE); + mockEntityRemoveService.remove.and.returnValue(mockResult); + component.entity = new Child("Test-Child"); const router = fixture.debugElement.injector.get(Router); - const dialogReturn: any = { afterClosed: () => of(true) }; - spyOn(dialogRef, "openDialog").and.returnValue(dialogReturn); - spyOn(mockedEntityMapper, "remove").and.resolveTo(); - spyOn(mockedEntityMapper, "save").and.resolveTo(); - spyOn(component, "navigateBack"); - const snackBarReturn: any = { onAction: () => of({}) }; - spyOn(snackBar, "open").and.returnValue(snackBarReturn); spyOn(router, "navigate"); component.removeEntity(); tick(); - expect(dialogRef.openDialog).toHaveBeenCalled(); - expect(mockedEntityMapper.remove).toHaveBeenCalledWith(testChild); - expect(snackBar.open).toHaveBeenCalled(); - expect(mockedEntityMapper.save).toHaveBeenCalledWith(testChild, true); expect(router.navigate).toHaveBeenCalled(); })); diff --git a/src/app/core/entity-components/entity-details/entity-details.component.ts b/src/app/core/entity-components/entity-details/entity-details.component.ts index 7b55a11a6a..8972a24256 100644 --- a/src/app/core/entity-components/entity-details/entity-details.component.ts +++ b/src/app/core/entity-components/entity-details/entity-details.component.ts @@ -1,19 +1,17 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { Location } from "@angular/common"; -import { MatSnackBar } from "@angular/material/snack-bar"; import { - PanelConfig, EntityDetailsConfig, Panel, PanelComponent, + PanelConfig, } from "./EntityDetailsConfig"; import { Entity, EntityConstructor } from "../../entity/model/entity"; import { School } from "../../../child-dev-project/schools/model/school"; import { EntityMapperService } from "../../entity/entity-mapper.service"; import { getUrlWithoutParams } from "../../../utils/utils"; import { Child } from "../../../child-dev-project/children/model/child"; -import { ConfirmationDialogService } from "../../confirmation-dialog/confirmation-dialog.service"; import { RecurringActivity } from "../../../child-dev-project/attendance/model/recurring-activity"; import { EntityPermissionsService, @@ -21,9 +19,13 @@ import { } from "../../permissions/entity-permissions.service"; import { User } from "../../user/user"; import { Note } from "../../../child-dev-project/notes/model/note"; -import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; +import { UntilDestroy } from "@ngneat/until-destroy"; import { RouteData } from "../../view/dynamic-routing/view-config.interface"; import { AnalyticsService } from "../../analytics/analytics.service"; +import { + EntityRemoveService, + RemoveResult, +} from "../../entity/entity-remove.service"; export const ENTITY_MAP: Map> = new Map< string, @@ -57,7 +59,7 @@ export class EntityDetailsComponent { operationType = OperationType; panels: Panel[] = []; - classNamesWithIcon: string; + iconName: string; config: EntityDetailsConfig; constructor( @@ -65,14 +67,13 @@ export class EntityDetailsComponent { private route: ActivatedRoute, private router: Router, private location: Location, - private snackBar: MatSnackBar, private analyticsService: AnalyticsService, - private confirmationDialog: ConfirmationDialogService, - private permissionService: EntityPermissionsService + private permissionService: EntityPermissionsService, + private entityRemoveService: EntityRemoveService ) { this.route.data.subscribe((data: RouteData) => { this.config = data.config; - this.classNamesWithIcon = "fa fa-" + data.config.icon + " fa-fw"; + this.iconName = data.config.icon; this.route.paramMap.subscribe((params) => this.loadEntity(params.get("id")) ); @@ -128,31 +129,14 @@ export class EntityDetailsComponent { } removeEntity() { - const dialogRef = this.confirmationDialog.openDialog( - $localize`:Delete confirmation title:Delete?`, - $localize`:Delete confirmation text:Are you sure you want to delete this ${this.config.entity} ?` - ); - - dialogRef.afterClosed().subscribe((confirmed) => { - const currentUrl = getUrlWithoutParams(this.router); - if (confirmed) { - this.entityMapperService - .remove(this.entity) - .then(() => this.navigateBack()) - .catch((err) => console.log("error", err)); - - const snackBarRef = this.snackBar.open( - $localize`:Deleted Entity information:Deleted Entity ${this.entity.toString()}`, - "Undo", - { duration: 8000 } - ); - snackBarRef - .onAction() - .pipe(untilDestroyed(this)) - .subscribe(() => { - this.entityMapperService.save(this.entity, true); - this.router.navigate([currentUrl]); - }); + const currentUrl = getUrlWithoutParams(this.router); + this.entityRemoveService.remove(this.entity).subscribe(async (result) => { + switch (result) { + case RemoveResult.REMOVED: + this.navigateBack(); + break; + case RemoveResult.UNDONE: + await this.router.navigate([currentUrl]); } }); } diff --git a/src/app/core/entity-components/entity-details/entity-details.module.ts b/src/app/core/entity-components/entity-details/entity-details.module.ts index c7bfe8d5e9..fe4dafeea8 100644 --- a/src/app/core/entity-components/entity-details/entity-details.module.ts +++ b/src/app/core/entity-components/entity-details/entity-details.module.ts @@ -2,7 +2,6 @@ import { NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; import { EntityDetailsComponent } from "./entity-details.component"; import { ReactiveFormsModule } from "@angular/forms"; -import { MatIconModule } from "@angular/material/icon"; import { MatTooltipModule } from "@angular/material/tooltip"; import { MatInputModule } from "@angular/material/input"; import { MatExpansionModule } from "@angular/material/expansion"; @@ -17,13 +16,13 @@ import { PermissionsModule } from "../../permissions/permissions.module"; import { FormComponent } from "./form/form.component"; import { EntityFormModule } from "../entity-form/entity-form.module"; import { Angulartics2Module } from "angulartics2"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; @NgModule({ declarations: [EntityDetailsComponent, FormComponent], imports: [ CommonModule, ReactiveFormsModule, - MatIconModule, MatInputModule, MatExpansionModule, ViewModule, @@ -37,6 +36,7 @@ import { Angulartics2Module } from "angulartics2"; MatTooltipModule, EntityFormModule, Angulartics2Module, + FontAwesomeModule, ], entryComponents: [FormComponent], }) diff --git a/src/app/core/entity-components/entity-details/form/form.component.spec.ts b/src/app/core/entity-components/entity-details/form/form.component.spec.ts index b0636faf09..3b1cb887e2 100644 --- a/src/app/core/entity-components/entity-details/form/form.component.spec.ts +++ b/src/app/core/entity-components/entity-details/form/form.component.spec.ts @@ -4,6 +4,12 @@ import { FormComponent } from "./form.component"; import { Child } from "../../../../child-dev-project/children/model/child"; import { Router } from "@angular/router"; import { RouterTestingModule } from "@angular/router/testing"; +import { EntityFormModule } from "../../entity-form/entity-form.module"; +import { ReactiveFormsModule } from "@angular/forms"; +import { EntitySchemaService } from "../../../entity/schema/entity-schema.service"; +import { AlertService } from "../../../alerts/alert.service"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { MockSessionModule } from "../../../session/mock-session.module"; describe("FormComponent", () => { let component: FormComponent; @@ -12,13 +18,21 @@ describe("FormComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [FormComponent], - imports: [RouterTestingModule], + imports: [ + RouterTestingModule, + EntityFormModule, + ReactiveFormsModule, + MockSessionModule.withState(), + MatSnackBarModule, + ], + providers: [EntitySchemaService, AlertService], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(FormComponent); component = fixture.componentInstance; + component.entity = new Child(); fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-form/entity-form.module.ts b/src/app/core/entity-components/entity-form/entity-form.module.ts index 072959356f..d0af1f7cb8 100644 --- a/src/app/core/entity-components/entity-form/entity-form.module.ts +++ b/src/app/core/entity-components/entity-form/entity-form.module.ts @@ -6,9 +6,9 @@ import { MatButtonModule } from "@angular/material/button"; import { FlexModule } from "@angular/flex-layout"; import { ViewModule } from "../../view/view.module"; import { PermissionsModule } from "../../permissions/permissions.module"; -import { MatIconModule } from "@angular/material/icon"; import { MatTooltipModule } from "@angular/material/tooltip"; import { MatFormFieldModule } from "@angular/material/form-field"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; @NgModule({ declarations: [EntityFormComponent], @@ -18,9 +18,9 @@ import { MatFormFieldModule } from "@angular/material/form-field"; FlexModule, ViewModule, PermissionsModule, - MatIconModule, MatTooltipModule, MatFormFieldModule, + FontAwesomeModule, ], providers: [EntityFormService], exports: [EntityFormComponent], diff --git a/src/app/core/entity-components/entity-form/entity-form/entity-form.component.html b/src/app/core/entity-components/entity-form/entity-form/entity-form.component.html index 56149c6537..5822b3d042 100644 --- a/src/app/core/entity-components/entity-form/entity-form/entity-form.component.html +++ b/src/app/core/entity-components/entity-form/entity-form/entity-form.component.html @@ -8,14 +8,14 @@
- + >
{{ listName }}

aria-label="Clear" (click)="filterString = ''; applyFilter('')" > - +
@@ -48,11 +48,11 @@

{{ listName }}

operation: operationType.CREATE }" > - + icon="plus-circle" + > Add New @@ -71,11 +71,11 @@ *ngIf="!rec.formGroup || rec.formGroup.disabled" (click)="edit(rec)" > - + icon="pen" + >
diff --git a/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.scss b/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.scss index ff87d23c58..130441153c 100644 --- a/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.scss +++ b/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.scss @@ -13,7 +13,7 @@ } .table-action-icon { - font-size: 1.5em; + font-size: 12pt; } .table-action-button { diff --git a/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.spec.ts b/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.spec.ts index 3b2e20026f..75586062cd 100644 --- a/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.spec.ts +++ b/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, fakeAsync, - flush, TestBed, tick, waitForAsync, @@ -25,9 +24,6 @@ import { Note } from "../../../../child-dev-project/notes/model/note"; import { AlertService } from "../../../alerts/alert.service"; import { FormBuilder, FormGroup } from "@angular/forms"; import { EntityFormService } from "../../entity-form/entity-form.service"; -import { Subject } from "rxjs"; -import { ConfirmationDialogService } from "../../../confirmation-dialog/confirmation-dialog.service"; -import { MatSnackBar } from "@angular/material/snack-bar"; import { genders } from "../../../../child-dev-project/children/model/genders"; import { LoggingService } from "../../../logging/logging.service"; import { MockSessionModule } from "../../../session/mock-session.module"; @@ -282,38 +278,6 @@ describe("EntitySubrecordComponent", () => { expect(row.formGroup).toBeFalsy(); }); - it("should save a deleted entity when clicking the popup", fakeAsync(() => { - const dialogService = TestBed.inject(ConfirmationDialogService); - const dialogObservable = new Subject(); - spyOn(dialogService, "openDialog").and.returnValue({ - afterClosed: () => dialogObservable, - } as any); - const snackbarService = TestBed.inject(MatSnackBar); - const snackbarObservable = new Subject(); - spyOn(snackbarService, "open").and.returnValue({ - onAction: () => snackbarObservable, - } as any); - spyOn(entityMapper, "remove").and.resolveTo(); - spyOn(entityMapper, "save").and.resolveTo(); - const child = new Child(); - component.records = [child]; - - component.delete({ record: child }); - tick(); - expect(component.records).toEqual([child]); - - dialogObservable.next(true); - tick(); - expect(entityMapper.remove).toHaveBeenCalledWith(child); - expect(component.records).toEqual([]); - - snackbarObservable.next(); - expect(entityMapper.save).toHaveBeenCalledWith(child, true); - expect(component.records).toEqual([child]); - - flush(); - })); - it("should create new entities and call the show entity function", fakeAsync(() => { const child = new Child(); component.newRecordFactory = () => child; diff --git a/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.ts b/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.ts index 56c74d178c..8d8851436f 100644 --- a/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.ts +++ b/src/app/core/entity-components/entity-subrecord/entity-subrecord/entity-subrecord.component.ts @@ -23,6 +23,10 @@ import { MatDialog } from "@angular/material/dialog"; import { EntityFormComponent } from "../../entity-form/entity-form/entity-form.component"; import { LoggingService } from "../../../logging/logging.service"; import { AnalyticsService } from "../../../analytics/analytics.service"; +import { + EntityRemoveService, + RemoveResult, +} from "../../../entity/entity-remove.service"; export interface TableRow { record: T; @@ -97,7 +101,8 @@ export class EntitySubrecordComponent implements OnChanges { private entityFormService: EntityFormService, private dialog: MatDialog, private analyticsService: AnalyticsService, - private loggingService: LoggingService + private loggingService: LoggingService, + private entityRemoveService: EntityRemoveService ) { this.mediaSubscription = this.media .asObservable() @@ -231,34 +236,21 @@ export class EntitySubrecordComponent implements OnChanges { * @param row The entity to be deleted. */ delete(row: TableRow) { - const dialogRef = this._confirmationDialog.openDialog( - $localize`:Confirmation dialog delete header:Delete?`, - $localize`:Delete confirmation message:Are you sure you want to delete this record?` - ); - - dialogRef.afterClosed().subscribe((confirmed) => { - if (confirmed) { - this._entityMapper - .remove(row.record) - .then(() => this.removeFromDataTable(row)); - - const snackBarRef = this._snackBar.open( - $localize`:Record deleted info:Record deleted`, - "Undo", - { - duration: 8000, - } - ); - snackBarRef - .onAction() - .pipe(untilDestroyed(this)) - .subscribe(() => { - this._entityMapper.save(row.record, true); + this.entityRemoveService + .remove(row.record, { + deletedEntityInformation: $localize`:Record deleted info:Record deleted`, + dialogText: $localize`:Delete confirmation message:Are you sure you want to delete this record?`, + }) + .subscribe((result) => { + switch (result) { + case RemoveResult.REMOVED: + this.removeFromDataTable(row); + break; + case RemoveResult.UNDONE: this.records.unshift(row.record); this.initFormGroups(); - }); - } - }); + } + }); } private removeFromDataTable(row: TableRow) { diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-age/edit-age.component.html b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-age/edit-age.component.html index 7bac622d6c..2b0472e1ce 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-age/edit-age.component.html +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-age/edit-age.component.html @@ -13,13 +13,13 @@ - + > { let component: EditAgeComponent; @@ -11,7 +16,16 @@ describe("EditAgeComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [EntityDetailsModule, NoopAnimationsModule], + imports: [ + NoopAnimationsModule, + MatFormFieldModule, + MatDatepickerModule, + FontAwesomeModule, + ReactiveFormsModule, + MatNativeDateModule, + FontAwesomeTestingModule, + MatInputModule, + ], declarations: [EditAgeComponent], }).compileComponents(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-boolean/edit-boolean.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-boolean/edit-boolean.component.spec.ts index 1c5b71af09..d8e6be9445 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-boolean/edit-boolean.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-boolean/edit-boolean.component.spec.ts @@ -1,9 +1,9 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditBooleanComponent } from "./edit-boolean.component"; -import { EntityDetailsModule } from "../../../entity-details/entity-details.module"; -import { FormControl, FormGroup } from "@angular/forms"; +import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { MatCheckboxModule } from "@angular/material/checkbox"; describe("EditBooleanComponent", () => { let component: EditBooleanComponent; @@ -11,7 +11,7 @@ describe("EditBooleanComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [EntityDetailsModule, NoopAnimationsModule], + imports: [NoopAnimationsModule, MatCheckboxModule, ReactiveFormsModule], declarations: [EditBooleanComponent], }).compileComponents(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-date/edit-date.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-date/edit-date.component.spec.ts index 790f9a38ed..45fa70584f 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-date/edit-date.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-date/edit-date.component.spec.ts @@ -1,9 +1,12 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditDateComponent } from "./edit-date.component"; -import { EntityDetailsModule } from "../../../entity-details/entity-details.module"; -import { FormControl, FormGroup } from "@angular/forms"; +import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatDatepickerModule } from "@angular/material/datepicker"; +import { MatInputModule } from "@angular/material/input"; +import { MatNativeDateModule } from "@angular/material/core"; describe("EditDateComponent", () => { let component: EditDateComponent; @@ -11,7 +14,14 @@ describe("EditDateComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [EntityDetailsModule, NoopAnimationsModule], + imports: [ + NoopAnimationsModule, + MatFormFieldModule, + ReactiveFormsModule, + MatDatepickerModule, + MatInputModule, + MatNativeDateModule, + ], declarations: [EditDateComponent], }).compileComponents(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-long-text/edit-long-text.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-long-text/edit-long-text.component.spec.ts index de1217dc37..3bd63009d7 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-long-text/edit-long-text.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-long-text/edit-long-text.component.spec.ts @@ -1,9 +1,10 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditLongTextComponent } from "./edit-long-text.component"; -import { EntityDetailsModule } from "../../../entity-details/entity-details.module"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; -import { FormControl, FormGroup } from "@angular/forms"; +import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; describe("EditLongTextComponent", () => { let component: EditLongTextComponent; @@ -11,7 +12,12 @@ describe("EditLongTextComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [EntityDetailsModule, NoopAnimationsModule], + imports: [ + NoopAnimationsModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + ], declarations: [EditLongTextComponent], }).compileComponents(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.html b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.html index d76b805c00..132810fa5f 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.html +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.html @@ -20,5 +20,5 @@ [title]="label" type="text" /> - + diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.spec.ts index d8f45ebb11..30b5e1851f 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditPhotoComponent } from "./edit-photo.component"; -import { EntityDetailsModule } from "../../../entity-details/entity-details.module"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { FormControl, FormGroup } from "@angular/forms"; import { SessionService } from "../../../../session/session-service/session.service"; @@ -14,7 +13,7 @@ describe("EditPhotoComponent", () => { beforeEach(async () => { mockSessionService = jasmine.createSpyObj(["getCurrentUser"]); await TestBed.configureTestingModule({ - imports: [EntityDetailsModule, NoopAnimationsModule], + imports: [NoopAnimationsModule], providers: [{ provide: SessionService, useValue: mockSessionService }], declarations: [EditPhotoComponent], }).compileComponents(); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.html b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.html index 7f2051e428..25a2970a49 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.html +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.html @@ -35,6 +35,6 @@ [linkDisabled]="true" > diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-text/edit-text.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-text/edit-text.component.spec.ts index d15f16fac4..7e6c04e2b0 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-text/edit-text.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-text/edit-text.component.spec.ts @@ -1,9 +1,10 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditTextComponent } from "./edit-text.component"; -import { FormControl, FormGroup } from "@angular/forms"; -import { EntityDetailsModule } from "../../../entity-details/entity-details.module"; +import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; describe("EditTextComponent", () => { let component: EditTextComponent; @@ -11,7 +12,12 @@ describe("EditTextComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [EntityDetailsModule, NoopAnimationsModule], + imports: [ + NoopAnimationsModule, + MatFormFieldModule, + ReactiveFormsModule, + MatInputModule, + ], declarations: [EditTextComponent], }).compileComponents(); }); diff --git a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.html b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.html index d47397ed6f..894ee4994d 100644 --- a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.html +++ b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.html @@ -21,12 +21,14 @@ class="chip" > -
+ > diff --git a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.scss b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.scss index 7cc37e08fc..d98647ef6f 100644 --- a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.scss +++ b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.scss @@ -9,7 +9,7 @@ border: 1px solid lightgray; } -.fa-minus-square-o { +.remove-item { margin: 0.15em; cursor: pointer; } diff --git a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.spec.ts b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.spec.ts index 9f5fd70fd1..e0f16f6d15 100644 --- a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.spec.ts @@ -170,6 +170,7 @@ describe("EntitySelectComponent", () => { }); it("should add an unselected entity to the filtered entities array", (done) => { + // TODO this is still throwing object unsubscribe error component.allEntities = testUsers; const selectedUser = testUsers[1]; let iteration = 0; diff --git a/src/app/core/entity-components/entity-utils/entity-utils.module.ts b/src/app/core/entity-components/entity-utils/entity-utils.module.ts index 2797efa561..84907d59a7 100644 --- a/src/app/core/entity-components/entity-utils/entity-utils.module.ts +++ b/src/app/core/entity-components/entity-utils/entity-utils.module.ts @@ -21,7 +21,6 @@ import { MatOptionModule } from "@angular/material/core"; import { MatSelectModule } from "@angular/material/select"; import { ReactiveFormsModule } from "@angular/forms"; import { ConfigurableEnumModule } from "../../configurable-enum/configurable-enum.module"; -import { MatIconModule } from "@angular/material/icon"; import { MatTooltipModule } from "@angular/material/tooltip"; import { MatInputModule } from "@angular/material/input"; import { ViewModule } from "../../view/view.module"; @@ -33,6 +32,7 @@ import { MatChipsModule } from "@angular/material/chips"; import { EditNumberComponent } from "./dynamic-form-components/edit-number/edit-number.component"; import { EntityFunctionPipe } from "./view-components/readonly-function/entity-function.pipe"; import { FlexLayoutModule } from "@angular/flex-layout"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { MatButtonModule } from "@angular/material/button"; @NgModule({ @@ -64,7 +64,6 @@ import { MatButtonModule } from "@angular/material/button"; MatSelectModule, ReactiveFormsModule, ConfigurableEnumModule, - MatIconModule, MatTooltipModule, MatInputModule, ViewModule, @@ -73,6 +72,7 @@ import { MatButtonModule } from "@angular/material/button"; MatAutocompleteModule, MatChipsModule, FlexLayoutModule, + FontAwesomeModule, MatButtonModule, ], entryComponents: [ diff --git a/src/app/core/entity-components/entity-utils/view-components/display-date/display-date.component.ts b/src/app/core/entity-components/entity-utils/view-components/display-date/display-date.component.ts index 63af04b870..60cae5b8a2 100644 --- a/src/app/core/entity-components/entity-utils/view-components/display-date/display-date.component.ts +++ b/src/app/core/entity-components/entity-utils/view-components/display-date/display-date.component.ts @@ -10,7 +10,7 @@ import { ViewComponent } from "../view-component"; template: `{{ entity[property] | date: format }}`, }) export class DisplayDateComponent extends ViewComponent { - format = "YYYY-MM-dd"; + format = "yyyy-MM-dd"; onInitFromDynamicConfig(config: ViewPropertyConfig) { super.onInitFromDynamicConfig(config); diff --git a/src/app/core/entity/entity-remove.service.spec.ts b/src/app/core/entity/entity-remove.service.spec.ts new file mode 100644 index 0000000000..a6b5f6c179 --- /dev/null +++ b/src/app/core/entity/entity-remove.service.spec.ts @@ -0,0 +1,132 @@ +import { TestBed } from "@angular/core/testing"; +import { EntityRemoveService, RemoveResult } from "./entity-remove.service"; +import { EntityMapperService } from "./entity-mapper.service"; +import { + MatSnackBar, + MatSnackBarDismiss, + MatSnackBarRef, + TextOnlySnackBar, +} from "@angular/material/snack-bar"; +import { ConfirmationDialogService } from "../confirmation-dialog/confirmation-dialog.service"; +import { Entity } from "./model/entity"; +import { NEVER, Observable, Subject } from "rxjs"; +import { MatDialogRef } from "@angular/material/dialog"; +import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog/confirmation-dialog.component"; +import { toArray } from "rxjs/operators"; + +describe("EntityRemoveService", () => { + let service: EntityRemoveService; + let mockEntityMapper: jasmine.SpyObj; + let snackBarSpy: jasmine.SpyObj; + let mockSnackBarRef: jasmine.SpyObj>; + let mockConfirmationDialog: jasmine.SpyObj; + let mockDialogRef: jasmine.SpyObj>; + let afterClosed: Subject; + + beforeEach(() => { + mockEntityMapper = jasmine.createSpyObj(["remove", "save"]); + snackBarSpy = jasmine.createSpyObj(["open"]); + mockSnackBarRef = jasmine.createSpyObj(["onAction", "afterDismissed"]); + mockConfirmationDialog = jasmine.createSpyObj(["openDialog"]); + mockDialogRef = jasmine.createSpyObj(["afterClosed"]); + afterClosed = new Subject(); + mockDialogRef.afterClosed.and.returnValue(afterClosed); + mockConfirmationDialog.openDialog.and.returnValue(mockDialogRef); + snackBarSpy.open.and.returnValue(mockSnackBarRef); + mockEntityMapper.remove.and.resolveTo(); + TestBed.configureTestingModule({ + providers: [ + { provide: EntityMapperService, useValue: mockEntityMapper }, + { provide: MatSnackBar, useValue: snackBarSpy }, + { + provide: ConfirmationDialogService, + useValue: mockConfirmationDialog, + }, + ], + }); + service = TestBed.inject(EntityRemoveService); + }); + + afterEach(() => { + afterClosed.complete(); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("emits once and closes when the user has cancelled", (done) => { + service + .remove(new Entity()) + .pipe(toArray()) + .subscribe( + (next) => { + expect(next).toEqual([RemoveResult.CANCELLED]); + }, + () => { + // intentionally empty + }, + () => { + expect(snackBarSpy.open).not.toHaveBeenCalled(); + expect(mockEntityMapper.remove).not.toHaveBeenCalled(); + done(); + } + ); + afterClosed.next(false); + }); + + it("deletes the entity and finishes if the action is never undone", (done) => { + // onAction is never called + mockSnackBarRef.onAction.and.returnValues(NEVER); + // mock that dialog is dismissed immediately + const afterDismissed = new Observable((subscriber) => + subscriber.next({} as MatSnackBarDismiss) + ); + mockSnackBarRef.afterDismissed.and.returnValue(afterDismissed); + service + .remove(new Entity()) + .pipe(toArray()) + .subscribe( + (next) => { + expect(next).toEqual([RemoveResult.REMOVED]); + }, + () => { + // intentionally empty + }, + () => { + expect(snackBarSpy.open).toHaveBeenCalled(); + expect(mockEntityMapper.remove).toHaveBeenCalled(); + done(); + } + ); + afterClosed.next(true); + }); + + it("emits twice when an entity was deleted and the user pressed undo", (done) => { + // Mock a snackbar where 'undo' is immediately pressed + const onSnackbarAction = new Observable((subscriber) => + subscriber.next() + ); + mockSnackBarRef.onAction.and.returnValue(onSnackbarAction); + mockSnackBarRef.afterDismissed.and.returnValue(NEVER); + mockEntityMapper.save.and.resolveTo(); + const entity = new Entity(); + service + .remove(entity) + .pipe(toArray()) + .subscribe( + (next) => { + expect(next).toEqual([RemoveResult.REMOVED, RemoveResult.UNDONE]); + }, + () => { + // intentionally empty + }, + () => { + expect(mockEntityMapper.remove).toHaveBeenCalled(); + expect(mockEntityMapper.save).toHaveBeenCalledWith(entity, true); + done(); + } + ); + afterClosed.next(true); + }); +}); diff --git a/src/app/core/entity/entity-remove.service.ts b/src/app/core/entity/entity-remove.service.ts new file mode 100644 index 0000000000..d058d04473 --- /dev/null +++ b/src/app/core/entity/entity-remove.service.ts @@ -0,0 +1,145 @@ +import { Injectable } from "@angular/core"; +import { ConfirmationDialogService } from "../confirmation-dialog/confirmation-dialog.service"; +import { EntityMapperService } from "./entity-mapper.service"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { Entity } from "./model/entity"; +import { Observable, race, Subject } from "rxjs"; +import { map } from "rxjs/operators"; + +/** + * All possible results when removing an entity + */ +export enum RemoveResult { + /** + * The user cancelled the action + */ + CANCELLED, + /** + * The entity was successfully removed + */ + REMOVED, + /** + * The user has undone the action and the entity + * now exists again + */ + UNDONE, +} + +/** + * Additional options that can be (partly) specified + * for the several titles + */ +export interface RemoveEntityTextOptions { + dialogTitle?: string; + dialogText?: string; + deletedEntityInformation?: string; +} + +/** + * A service that can be used to safely remove an entity + * which includes: + *
    + *
  • Displaying a confirmation dialog and asking the user whether or not he + * wants to delete the entity + *
  • Removing the entity in the `EntityMapperService` (if he chose so) + *
  • Opening a snack bar to allow the user to undo the action + *
+ */ +@Injectable({ + providedIn: "root", +}) +export class EntityRemoveService { + constructor( + private confirmationDialog: ConfirmationDialogService, + private entityMapper: EntityMapperService, + private snackBar: MatSnackBar + ) {} + + /** + * Removes the entity after displaying a confirmation dialog. + * The returned observable will emit once or twice, depending on how the + * user chose. + *
    + *
  • When the user chose 'no', the subject will emit once with + * {@link RemoveResult.CANCELLED CANCELLED}. + *
  • When the user chose 'yes', the subject will emit with the result + * {@link RemoveResult.REMOVED REMOVED}. If the user doesn't undo, the subject will complete + * once the snack bar has disappeared. If the user chose undo, the subject will + * emit with the result {@link RemoveResult.UNDONE UNDONE}. + *
+ *
+ * This method takes care of saving the entity as if one were to call the resp. methods + * of the `EntityMapperService`. It also restores the entity when the user chose to + * undo. All of the aforementioned results will only be called if the actions have already + * been taken. + *
+ * You do not need to unsubscribe from this method as the observable will be closed on all + * possible paths. + * @param entity The entity to remove + * @param textOptions Options that you can specify to override the default options. + * You can only specify some of the options, the options that you don't specify will then be + * the default ones. + */ + remove( + entity: E, + textOptions?: RemoveEntityTextOptions + ): Observable { + const subject = new Subject(); + const dialogTitle = + textOptions?.dialogTitle || $localize`:Delete confirmation title:Delete?`; + const dialogText = + textOptions?.dialogText || + $localize`:Delete confirmation text:Are you sure you want to delete this ${entity.getType()}?`; + const dialogRef = this.confirmationDialog.openDialog( + dialogTitle, + dialogText + ); + + dialogRef.afterClosed().subscribe(async (confirmed) => { + if (confirmed) { + const snackBarTitle = + textOptions?.deletedEntityInformation || + $localize`:Deleted Entity information:Deleted Entity ${entity.toString()}`; + await this.removeEntityAndOpenSnackBar(entity, snackBarTitle, subject); + } else { + subject.next(RemoveResult.CANCELLED); + subject.complete(); + } + }); + return subject.asObservable(); + } + + /** + * Decoupled from the main method for readability + * @param entity The entity to remove + * @param snackBarTitle The title of the snack-bar + * @param resultSender The sender that informs when the user + * has clicked 'undo' and that completes once no undo is possible + * @private + */ + private async removeEntityAndOpenSnackBar( + entity: Entity, + snackBarTitle: string, + resultSender: Subject + ) { + await this.entityMapper.remove(entity); + resultSender.next(RemoveResult.REMOVED); + const snackBarRef = this.snackBar.open( + snackBarTitle, + $localize`:Undo deleting an entity:Undo`, + { + duration: 8000, + } + ); + race( + snackBarRef.onAction().pipe(map(() => true)), + snackBarRef.afterDismissed().pipe(map(() => false)) + ).subscribe(async (next) => { + if (next) { + await this.entityMapper.save(entity, true); + resultSender.next(RemoveResult.UNDONE); + } + resultSender.complete(); + }); + } +} diff --git a/src/app/core/form-dialog/form-dialog-wrapper/form-dialog-wrapper.component.ts b/src/app/core/form-dialog/form-dialog-wrapper/form-dialog-wrapper.component.ts index 741b80cdf5..6bd32cae42 100644 --- a/src/app/core/form-dialog/form-dialog-wrapper/form-dialog-wrapper.component.ts +++ b/src/app/core/form-dialog/form-dialog-wrapper/form-dialog-wrapper.component.ts @@ -10,11 +10,13 @@ import { EntityMapperService } from "../../entity/entity-mapper.service"; import { Entity } from "../../entity/model/entity"; import { MatDialogRef } from "@angular/material/dialog"; import { getUrlWithoutParams } from "../../../utils/utils"; -import { MatSnackBar } from "@angular/material/snack-bar"; import { Router } from "@angular/router"; -import { ConfirmationDialogService } from "../../confirmation-dialog/confirmation-dialog.service"; import { OperationType } from "../../permissions/entity-permissions.service"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; +import { + EntityRemoveService, + RemoveResult, +} from "../../entity/entity-remove.service"; /** * Use `` in your form templates to handle the saving and resetting of the edited entity. @@ -80,8 +82,7 @@ export class FormDialogWrapperComponent implements AfterViewInit { private entityMapper: EntityMapperService, private matDialogRef: MatDialogRef, private router: Router, - private snackBar: MatSnackBar, - private confirmationDialog: ConfirmationDialogService + private entityRemoveService: EntityRemoveService ) {} ngAfterViewInit() { @@ -126,30 +127,15 @@ export class FormDialogWrapperComponent implements AfterViewInit { this.onClose.emit(undefined); } - public async delete() { - const dialogRef = this.confirmationDialog.openDialog( - "Delete?", - "Are you sure you want to delete this object?" - ); - - dialogRef.afterClosed().subscribe(async (confirmed: any) => { - const currentUrl = getUrlWithoutParams(this.router); - if (confirmed) { - await this.entityMapper.remove(this.entity); - this.onClose.emit(undefined); - - const snackBarRef = this.snackBar.open( - $localize`:Deleted Entity information:Deleted Entity ${this.entity.toString()}`, - "Undo", - { duration: 8000 } - ); - snackBarRef - .onAction() - .pipe(untilDestroyed(this)) - .subscribe(() => { - this.entityMapper.save(this.entity, true); - this.router.navigate([currentUrl]); - }); + public delete() { + const currentUrl = getUrlWithoutParams(this.router); + this.entityRemoveService.remove(this.entity).subscribe((result) => { + switch (result) { + case RemoveResult.REMOVED: + this.onClose.emit(undefined); + break; + case RemoveResult.UNDONE: + this.router.navigate([currentUrl]); } }); } diff --git a/src/app/core/icons/font-awesome-icons.module.ts b/src/app/core/icons/font-awesome-icons.module.ts deleted file mode 100644 index 3412e7c973..0000000000 --- a/src/app/core/icons/font-awesome-icons.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { MatIconModule, MatIconRegistry } from "@angular/material/icon"; - -/** - * A wrapper module to use MatIcon with font-awesome as default icon font set up. - * - * This allows encapsulated configuration and loading e.g. in storybook. - */ -@NgModule({ - declarations: [], - imports: [CommonModule, MatIconModule], - exports: [MatIconModule], -}) -export class FontAwesomeIconsModule { - constructor(public matIconRegistry: MatIconRegistry) { - matIconRegistry.registerFontClassAlias("fontawesome", "fa"); - matIconRegistry.setDefaultFontSetClass("fa"); - } -} diff --git a/src/app/core/latest-changes/changelog/changelog.component.html b/src/app/core/latest-changes/changelog/changelog.component.html index dc5b19eb9b..4677f906ca 100644 --- a/src/app/core/latest-changes/changelog/changelog.component.html +++ b/src/app/core/latest-changes/changelog/changelog.component.html @@ -25,11 +25,11 @@

Latest Changes

}} {{ changelog?.published_at | date }} {{ changelog?.tag_name }} diff --git a/src/app/core/latest-changes/changelog/changelog.component.spec.ts b/src/app/core/latest-changes/changelog/changelog.component.spec.ts index f9c7a217a7..24220a1412 100644 --- a/src/app/core/latest-changes/changelog/changelog.component.spec.ts +++ b/src/app/core/latest-changes/changelog/changelog.component.spec.ts @@ -26,6 +26,7 @@ import { LatestChangesModule } from "../latest-changes.module"; import { SwUpdate } from "@angular/service-worker"; import { MarkdownModule } from "ngx-markdown"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("ChangelogComponent", () => { let component: ChangelogComponent; @@ -51,6 +52,7 @@ describe("ChangelogComponent", () => { LatestChangesModule, MarkdownModule.forRoot(), NoopAnimationsModule, + FontAwesomeTestingModule, ], providers: [ { provide: MatDialogRef, useValue: {} }, diff --git a/src/app/core/latest-changes/latest-changes-dialog.service.ts b/src/app/core/latest-changes/latest-changes-dialog.service.ts index 6f0364c059..6854c4023a 100644 --- a/src/app/core/latest-changes/latest-changes-dialog.service.ts +++ b/src/app/core/latest-changes/latest-changes-dialog.service.ts @@ -27,7 +27,7 @@ import { LatestChangesService } from "./latest-changes.service"; */ @Injectable() export class LatestChangesDialogService { - private static STORAGE_KEY = "AppVersion"; + public static VERSION_KEY = "AppVersion"; constructor( private dialog: MatDialog, @@ -60,13 +60,13 @@ export class LatestChangesDialogService { */ public showLatestChangesIfUpdated() { const previousVersion = window.localStorage.getItem( - LatestChangesDialogService.STORAGE_KEY + LatestChangesDialogService.VERSION_KEY ); if (previousVersion && this.getCurrentVersion() !== previousVersion) { this.showLatestChanges(previousVersion); } window.localStorage.setItem( - LatestChangesDialogService.STORAGE_KEY, + LatestChangesDialogService.VERSION_KEY, this.getCurrentVersion() ); } diff --git a/src/app/core/latest-changes/latest-changes.module.ts b/src/app/core/latest-changes/latest-changes.module.ts index bc1d5c3bd8..11c0c83607 100644 --- a/src/app/core/latest-changes/latest-changes.module.ts +++ b/src/app/core/latest-changes/latest-changes.module.ts @@ -15,7 +15,7 @@ * along with ndb-core. If not, see . */ -import { NgModule } from "@angular/core"; +import { InjectionToken, NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; import { AppVersionComponent } from "./app-version/app-version.component"; import { AlertsModule } from "../alerts/alerts.module"; @@ -29,10 +29,15 @@ import { SwUpdate } from "@angular/service-worker"; import { UpdateManagerService } from "./update-manager.service"; import { FlexModule } from "@angular/flex-layout"; import { MarkdownModule } from "ngx-markdown"; -import { MatIconModule } from "@angular/material/icon"; import { MatCardModule } from "@angular/material/card"; import { LatestChangesDialogService } from "./latest-changes-dialog.service"; import { LatestChangesService } from "./latest-changes.service"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; + +// Following this post to allow testing of the location object: https://itnext.io/testing-browser-window-location-in-angular-application-e4e8388508ff +export const LOCATION_TOKEN = new InjectionToken( + "Window location object" +); /** * Displaying app version and changelog information to the user @@ -54,8 +59,8 @@ import { LatestChangesService } from "./latest-changes.service"; HttpClientModule, FlexModule, MarkdownModule, - MatIconModule, MatCardModule, + FontAwesomeModule, ], declarations: [AppVersionComponent, ChangelogComponent], exports: [AppVersionComponent], @@ -63,6 +68,7 @@ import { LatestChangesService } from "./latest-changes.service"; LatestChangesService, LatestChangesDialogService, UpdateManagerService, + { provide: LOCATION_TOKEN, useValue: window.location }, ], }) export class LatestChangesModule { @@ -71,9 +77,9 @@ export class LatestChangesModule { private latestChangesDialogService: LatestChangesDialogService, private updateManagerService: UpdateManagerService ) { - this.latestChangesDialogService.showLatestChangesIfUpdated(); - this.updateManagerService.notifyUserWhenUpdateAvailable(); this.updateManagerService.regularlyCheckForUpdates(); + + this.latestChangesDialogService.showLatestChangesIfUpdated(); } } diff --git a/src/app/core/latest-changes/update-manager.service.spec.ts b/src/app/core/latest-changes/update-manager.service.spec.ts new file mode 100644 index 0000000000..6ffa321afa --- /dev/null +++ b/src/app/core/latest-changes/update-manager.service.spec.ts @@ -0,0 +1,143 @@ +import { UpdateManagerService } from "./update-manager.service"; +import { + discardPeriodicTasks, + fakeAsync, + TestBed, + tick, +} from "@angular/core/testing"; +import { ApplicationRef } from "@angular/core"; +import { SwUpdate, UpdateActivatedEvent } from "@angular/service-worker"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { LoggingService } from "../logging/logging.service"; +import { LatestChangesDialogService } from "./latest-changes-dialog.service"; +import { LOCATION_TOKEN } from "./latest-changes.module"; +import { Subject } from "rxjs"; + +describe("UpdateManagerService", () => { + let service: UpdateManagerService; + let location: jasmine.SpyObj; + let swUpdate: jasmine.SpyObj; + let updateSubject: Subject; + let snackBar: jasmine.SpyObj; + let snackBarAction: Subject; + let appRef: jasmine.SpyObj; + let stableSubject: Subject; + + beforeEach(() => { + location = jasmine.createSpyObj(["reload"]); + updateSubject = new Subject(); + swUpdate = jasmine.createSpyObj(["checkForUpdate"], { + available: updateSubject, + isEnabled: true, + }); + snackBar = jasmine.createSpyObj(["open"]); + snackBarAction = new Subject(); + snackBar.open.and.returnValue({ + onAction: () => snackBarAction.asObservable(), + } as any); + stableSubject = new Subject(); + appRef = jasmine.createSpyObj([], { isStable: stableSubject }); + + TestBed.configureTestingModule({ + providers: [ + UpdateManagerService, + { provide: ApplicationRef, useValue: appRef }, + { provide: SwUpdate, useValue: swUpdate }, + { provide: MatSnackBar, useValue: snackBar }, + { provide: LoggingService, useValue: {} }, + { provide: LOCATION_TOKEN, useValue: location }, + ], + }); + + service = TestBed.inject(UpdateManagerService); + }); + + it("should create", () => { + expect(service).toBeTruthy(); + }); + + it("should show a popup that allows to reload the page when an update is available", fakeAsync(() => { + service.notifyUserWhenUpdateAvailable(); + // notify about new update + updateSubject.next(); + tick(); + + expect(snackBar.open).toHaveBeenCalled(); + + // user activates update + snackBarAction.next(undefined); + tick(); + + expect(location.reload).toHaveBeenCalled(); + })); + + it("should reload the page during construction if noted in the local storage", () => { + const version = "1.1.1"; + window.localStorage.setItem( + LatestChangesDialogService.VERSION_KEY, + "update-" + version + ); + + // tslint:disable-next-line:no-unused-expression + new UpdateManagerService(null, null, null, null, location); + + expect(location.reload).toHaveBeenCalled(); + expect( + window.localStorage.getItem(LatestChangesDialogService.VERSION_KEY) + ).toBe(version); + }); + + it("should set the note for reloading the app on next startup and remove it if user triggers reload manually", fakeAsync(() => { + const version = "1.1.1"; + window.localStorage.setItem( + LatestChangesDialogService.VERSION_KEY, + version + ); + service.notifyUserWhenUpdateAvailable(); + updateSubject.next(); + + expect( + window.localStorage.getItem(LatestChangesDialogService.VERSION_KEY) + ).toBe("update-" + version); + + // reload is triggered by clicking button on the snackbar + snackBarAction.next(); + + expect( + window.localStorage.getItem(LatestChangesDialogService.VERSION_KEY) + ).toBe(version); + })); + + it("should check for updates once on startup and then every hour", fakeAsync(() => { + service.regularlyCheckForUpdates(); + tick(); + + expect(swUpdate.checkForUpdate).not.toHaveBeenCalled(); + + stableSubject.next(false); + tick(); + + expect(swUpdate.checkForUpdate).not.toHaveBeenCalled(); + + stableSubject.next(true); + tick(); + + expect(swUpdate.checkForUpdate).toHaveBeenCalledTimes(1); + + stableSubject.next(true); + tick(); + + expect(swUpdate.checkForUpdate).toHaveBeenCalledTimes(1); + + // One hour later + tick(1000 * 60 * 60); + + expect(swUpdate.checkForUpdate).toHaveBeenCalledTimes(2); + + tick(1000 * 60 * 60); + + expect(swUpdate.checkForUpdate).toHaveBeenCalledTimes(3); + + discardPeriodicTasks(); + })); +}); diff --git a/src/app/core/latest-changes/update-manager.service.ts b/src/app/core/latest-changes/update-manager.service.ts index b04ba5c344..3cb2092e3e 100644 --- a/src/app/core/latest-changes/update-manager.service.ts +++ b/src/app/core/latest-changes/update-manager.service.ts @@ -15,12 +15,14 @@ * along with ndb-core. If not, see . */ -import { ApplicationRef, Injectable } from "@angular/core"; +import { ApplicationRef, Inject, Injectable } from "@angular/core"; import { SwUpdate } from "@angular/service-worker"; import { first } from "rxjs/operators"; import { concat, interval } from "rxjs"; import { MatSnackBar } from "@angular/material/snack-bar"; import { LoggingService } from "../logging/logging.service"; +import { LatestChangesDialogService } from "./latest-changes-dialog.service"; +import { LOCATION_TOKEN } from "./latest-changes.module"; /** * Check with the server whether a new version of the app is available in order to notify the user. @@ -37,8 +39,20 @@ export class UpdateManagerService { private appRef: ApplicationRef, private updates: SwUpdate, private snackBar: MatSnackBar, - private logger: LoggingService - ) {} + private logger: LoggingService, + @Inject(LOCATION_TOKEN) private location: Location + ) { + const currentVersion: string = window.localStorage.getItem( + LatestChangesDialogService.VERSION_KEY + ); + if (currentVersion && currentVersion.startsWith("update-")) { + window.localStorage.setItem( + LatestChangesDialogService.VERSION_KEY, + currentVersion.replace("update-", "") + ); + this.location.reload(); + } + } /** * Display a notification to the user in case a new app version is detected by the ServiceWorker. @@ -78,12 +92,25 @@ export class UpdateManagerService { } private showUpdateNotification() { + const currentVersion: string = window.localStorage.getItem( + LatestChangesDialogService.VERSION_KEY + ); + window.localStorage.setItem( + LatestChangesDialogService.VERSION_KEY, + "update-" + currentVersion + ); + this.notificationRef = this.snackBar.open( $localize`A new version of the app is available!`, $localize`:Action that a user can update the app with:Update` ); this.notificationRef.onAction().subscribe(() => { - location.reload(); + window.localStorage.setItem( + LatestChangesDialogService.VERSION_KEY, + currentVersion + ); + + this.location.reload(); }); } } diff --git a/src/app/core/markdown-page/markdown-page/markdown-page.component.spec.ts b/src/app/core/markdown-page/markdown-page/markdown-page.component.spec.ts index 319155f320..29834b186e 100644 --- a/src/app/core/markdown-page/markdown-page/markdown-page.component.spec.ts +++ b/src/app/core/markdown-page/markdown-page/markdown-page.component.spec.ts @@ -5,6 +5,8 @@ import { ActivatedRoute } from "@angular/router"; import { BehaviorSubject } from "rxjs"; import { MarkdownPageConfig } from "../MarkdownPageConfig"; import { RouteData } from "../../view/dynamic-routing/view-config.interface"; +import { MarkdownModule } from "ngx-markdown"; +import { HttpClient, HttpClientModule } from "@angular/common/http"; describe("HowToComponent", () => { let component: MarkdownPageComponent; @@ -20,6 +22,10 @@ describe("HowToComponent", () => { TestBed.configureTestingModule({ declarations: [MarkdownPageComponent], + imports: [ + HttpClientModule, + MarkdownModule.forRoot({ loader: HttpClient }), + ], providers: [ { provide: ActivatedRoute, useValue: { data: mockRouteData } }, ], diff --git a/src/app/core/navigation/navigation.module.ts b/src/app/core/navigation/navigation.module.ts index 6e21a9b7ff..69406cd7df 100644 --- a/src/app/core/navigation/navigation.module.ts +++ b/src/app/core/navigation/navigation.module.ts @@ -21,11 +21,12 @@ import { NavigationComponent } from "./navigation/navigation.component"; import { SessionModule } from "../session/session.module"; import { RouterModule } from "@angular/router"; import { MatButtonModule } from "@angular/material/button"; -import { MatIconModule } from "@angular/material/icon"; import { MatListModule } from "@angular/material/list"; import { MatTooltipModule } from "@angular/material/tooltip"; import { Angulartics2Module } from "angulartics2"; import { ConfigModule } from "../config/config.module"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { ViewModule } from "../view/view.module"; /** * Manages the main app navigation menu @@ -37,11 +38,12 @@ import { ConfigModule } from "../config/config.module"; SessionModule, RouterModule, MatListModule, - MatIconModule, MatButtonModule, MatTooltipModule, Angulartics2Module, ConfigModule, + FontAwesomeModule, + ViewModule, ], declarations: [NavigationComponent], exports: [NavigationComponent], diff --git a/src/app/core/navigation/navigation/navigation.component.html b/src/app/core/navigation/navigation/navigation.component.html index 15edafb885..cf5eaaff1d 100644 --- a/src/app/core/navigation/navigation/navigation.component.html +++ b/src/app/core/navigation/navigation/navigation.component.html @@ -25,7 +25,7 @@ [routerLink]="[item.link]" > {{ item.label }} diff --git a/src/app/core/navigation/navigation/navigation.component.scss b/src/app/core/navigation/navigation/navigation.component.scss index 605d9c9ff8..566f225c55 100644 --- a/src/app/core/navigation/navigation/navigation.component.scss +++ b/src/app/core/navigation/navigation/navigation.component.scss @@ -25,3 +25,7 @@ .nav-history { text-align: center; } + +.nav-icon { + margin-right: 6px; +} diff --git a/src/app/core/navigation/navigation/navigation.component.spec.ts b/src/app/core/navigation/navigation/navigation.component.spec.ts index e37ded3260..f791854e73 100644 --- a/src/app/core/navigation/navigation/navigation.component.spec.ts +++ b/src/app/core/navigation/navigation/navigation.component.spec.ts @@ -21,7 +21,6 @@ import { NavigationComponent } from "./navigation.component"; import { RouterTestingModule } from "@angular/router/testing"; import { MenuItem } from "../menu-item"; import { MatDividerModule } from "@angular/material/divider"; -import { MatIconModule } from "@angular/material/icon"; import { MatListModule } from "@angular/material/list"; import { ConfigService } from "../../config/config.service"; import { BehaviorSubject } from "rxjs"; @@ -47,12 +46,7 @@ describe("NavigationComponent", () => { mockUserRoleGuard.canActivate.and.returnValue(true); TestBed.configureTestingModule({ - imports: [ - RouterTestingModule, - MatIconModule, - MatDividerModule, - MatListModule, - ], + imports: [RouterTestingModule, MatDividerModule, MatListModule], declarations: [NavigationComponent], providers: [ { provide: UserRoleGuard, useValue: mockUserRoleGuard }, diff --git a/src/app/core/permissions/disabled-wrapper/disabled-wrapper.component.spec.ts b/src/app/core/permissions/disabled-wrapper/disabled-wrapper.component.spec.ts index 64858e83fb..4caf11a7a5 100644 --- a/src/app/core/permissions/disabled-wrapper/disabled-wrapper.component.spec.ts +++ b/src/app/core/permissions/disabled-wrapper/disabled-wrapper.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { DisabledWrapperComponent } from "./disabled-wrapper.component"; +import { MatTooltipModule } from "@angular/material/tooltip"; describe("DisabledWrapperComponent", () => { let component: DisabledWrapperComponent; @@ -10,6 +11,7 @@ describe("DisabledWrapperComponent", () => { waitForAsync(() => { TestBed.configureTestingModule({ declarations: [DisabledWrapperComponent], + imports: [MatTooltipModule], }).compileComponents(); }) ); diff --git a/src/app/core/session/session-service/synced-session.service.spec.ts b/src/app/core/session/session-service/synced-session.service.spec.ts index 2b0cac5839..703dfbcaca 100644 --- a/src/app/core/session/session-service/synced-session.service.spec.ts +++ b/src/app/core/session/session-service/synced-session.service.spec.ts @@ -32,6 +32,7 @@ import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { DatabaseUser } from "./local-user"; import { TEST_PASSWORD, TEST_USER } from "../mock-session.module"; import { testSessionServiceImplementation } from "./session.service.spec"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("SyncedSessionService", () => { let sessionService: SyncedSessionService; @@ -52,7 +53,11 @@ describe("SyncedSessionService", () => { mockHttpClient = jasmine.createSpyObj(["post", "delete"]); mockHttpClient.delete.and.returnValue(of()); TestBed.configureTestingModule({ - imports: [MatSnackBarModule, NoopAnimationsModule], + imports: [ + MatSnackBarModule, + NoopAnimationsModule, + FontAwesomeTestingModule, + ], providers: [ EntitySchemaService, AlertService, diff --git a/src/app/core/sync-status/background-processing-indicator/background-processing-indicator.component.html b/src/app/core/sync-status/background-processing-indicator/background-processing-indicator.component.html index 6d7521fa7a..a4bb75594c 100644 --- a/src/app/core/sync-status/background-processing-indicator/background-processing-indicator.component.html +++ b/src/app/core/sync-status/background-processing-indicator/background-processing-indicator.component.html @@ -10,7 +10,7 @@ matBadgeColor="accent" [matBadgeHidden]="allTasksFinished | async" > - + @@ -31,11 +31,11 @@ [diameter]="20" class="process-spinner" > - + >
{{ process.title }} diff --git a/src/app/core/sync-status/background-processing-indicator/background-processing-indicator.component.spec.ts b/src/app/core/sync-status/background-processing-indicator/background-processing-indicator.component.spec.ts index dca7d3c2ce..e4f45c36c0 100644 --- a/src/app/core/sync-status/background-processing-indicator/background-processing-indicator.component.spec.ts +++ b/src/app/core/sync-status/background-processing-indicator/background-processing-indicator.component.spec.ts @@ -3,11 +3,12 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { BackgroundProcessingIndicatorComponent } from "./background-processing-indicator.component"; import { MatMenuModule } from "@angular/material/menu"; import { MatTooltipModule } from "@angular/material/tooltip"; -import { MatIconModule } from "@angular/material/icon"; import { MatBadgeModule } from "@angular/material/badge"; import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { EMPTY, of } from "rxjs"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("BackgroundProcessingIndicatorComponent", () => { let component: BackgroundProcessingIndicatorComponent; @@ -19,10 +20,11 @@ describe("BackgroundProcessingIndicatorComponent", () => { imports: [ MatMenuModule, MatTooltipModule, - MatIconModule, MatBadgeModule, MatProgressSpinnerModule, NoopAnimationsModule, + FontAwesomeModule, + FontAwesomeTestingModule, ], declarations: [BackgroundProcessingIndicatorComponent], }).compileComponents(); diff --git a/src/app/core/sync-status/sync-status.module.ts b/src/app/core/sync-status/sync-status.module.ts index 898100b9fc..8d9e5cff0a 100644 --- a/src/app/core/sync-status/sync-status.module.ts +++ b/src/app/core/sync-status/sync-status.module.ts @@ -22,7 +22,6 @@ import { SessionModule } from "../session/session.module"; import { AlertsModule } from "../alerts/alerts.module"; import { MatButtonModule } from "@angular/material/button"; import { MatDialogModule } from "@angular/material/dialog"; -import { MatIconModule } from "@angular/material/icon"; import { MatProgressBarModule } from "@angular/material/progress-bar"; import { InitialSyncDialogComponent } from "./sync-status/initial-sync-dialog.component"; import { MatBadgeModule } from "@angular/material/badge"; @@ -31,13 +30,13 @@ import { BackgroundProcessingIndicatorComponent } from "./background-processing- import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { FlexModule } from "@angular/flex-layout"; import { MatTooltipModule } from "@angular/material/tooltip"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; @NgModule({ imports: [ CommonModule, SessionModule, AlertsModule, - MatIconModule, MatButtonModule, MatDialogModule, MatProgressBarModule, @@ -46,6 +45,7 @@ import { MatTooltipModule } from "@angular/material/tooltip"; MatProgressSpinnerModule, FlexModule, MatTooltipModule, + FontAwesomeModule, ], declarations: [ InitialSyncDialogComponent, diff --git a/src/app/core/sync-status/sync-status/sync-status.component.spec.ts b/src/app/core/sync-status/sync-status/sync-status.component.spec.ts index bd40731e00..9cd043f3e7 100644 --- a/src/app/core/sync-status/sync-status/sync-status.component.spec.ts +++ b/src/app/core/sync-status/sync-status/sync-status.component.spec.ts @@ -26,6 +26,7 @@ import { BehaviorSubject } from "rxjs"; import { take } from "rxjs/operators"; import { BackgroundProcessState } from "../background-process-state.interface"; import { SyncStatusModule } from "../sync-status.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("SyncStatusComponent", () => { let component: SyncStatusComponent; @@ -52,7 +53,11 @@ describe("SyncStatusComponent", () => { mockIndexingService = { indicesRegistered: new BehaviorSubject([]) }; TestBed.configureTestingModule({ - imports: [SyncStatusModule, NoopAnimationsModule], + imports: [ + SyncStatusModule, + NoopAnimationsModule, + FontAwesomeTestingModule, + ], providers: [ { provide: SessionService, useValue: mockSessionService }, { provide: DatabaseIndexingService, useValue: mockIndexingService }, diff --git a/src/app/core/ui/primary-action/primary-action.component.html b/src/app/core/ui/primary-action/primary-action.component.html index 8fec60ad39..a052f869ca 100644 --- a/src/app/core/ui/primary-action/primary-action.component.html +++ b/src/app/core/ui/primary-action/primary-action.component.html @@ -10,5 +10,5 @@ operation: operationType.CREATE }" > - + diff --git a/src/app/core/ui/primary-action/primary-action.component.spec.ts b/src/app/core/ui/primary-action/primary-action.component.spec.ts index df3e55c4df..117ef104a7 100644 --- a/src/app/core/ui/primary-action/primary-action.component.spec.ts +++ b/src/app/core/ui/primary-action/primary-action.component.spec.ts @@ -6,6 +6,7 @@ import { MatDialogModule } from "@angular/material/dialog"; import { FormDialogModule } from "../../form-dialog/form-dialog.module"; import { PermissionsModule } from "../../permissions/permissions.module"; import { MockSessionModule } from "../../session/mock-session.module"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; describe("PrimaryActionComponent", () => { let component: PrimaryActionComponent; @@ -19,6 +20,7 @@ describe("PrimaryActionComponent", () => { MatButtonModule, FormDialogModule, PermissionsModule, + FontAwesomeTestingModule, MockSessionModule.withState(), ], }).compileComponents(); diff --git a/src/app/core/ui/search/search.component.html b/src/app/core/ui/search/search.component.html index 8c1f60aef5..e19265f7f6 100644 --- a/src/app/core/ui/search/search.component.html +++ b/src/app/core/ui/search/search.component.html @@ -1,7 +1,7 @@