diff --git a/README.md b/README.md
index 91366aacc7..a8fdc23eab 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,7 @@
## Documentation
-If you want to get started on how to use this project, you can start with our **[Getting Started-Document](./docs/0_GettingStarted.md)**.
-
-The complete **[documentation](./docs/README.md)** for this repository can be found here: *./docs/README.md*.
+If you want to get started on how to use this project, you can start with our complete **[documentation](https://documentation.dbildungscloud.dev/docs/category/nuxt-client)**.
## Related repositories
diff --git a/docs/0_GettingStarted.md b/docs/0_GettingStarted.md
deleted file mode 100644
index e384bdb61c..0000000000
--- a/docs/0_GettingStarted.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# Getting Started
-
-## Development Setup
-
-> **Note:** Please **_don't use yarn_** !!! We decided to use npm across all of our repositories.
-
-In order to run this client, you need to have the [legacy-client](https://github.com/hpi-schul-cloud/schulcloud-client) and [schulcloud-server](https://github.com/hpi-schul-cloud/schulcloud-server) set up and running. See for documentation on how to do that in the respective repositories.
-
-### Start the Server
-
-1. Install the required dependencies:
-
- ```sh
- npm ci
- ```
-
-2. Start the development server:
-
- ```sh
- npm run serve
- ```
-
- By default the server will listen on the URL [http://localhost:4000](http://localhost:4000)
-
-### Unit Tests
-
-```bash
-# Run all (unit) tests
-npm run test
-```
-
-### Lint
-
-```bash
-npm run lint
-```
-
-## Editor Setup
-
-We are using Visual Studio Code as our default deveopment-IDE. In /.vscode you can find two templates to setup your IDE:
-
-- `launch.default.json` (copy its content and us it in `launch.json`)
-- `settings.default.json` (copy its content and us it in `settings.json`)
-
-For a list of recommended Visual Studio Code extensions please refer to `extensions.json`.
diff --git a/docs/1_CodeConventions.md b/docs/1_CodeConventions.md
deleted file mode 100644
index 25323e1a95..0000000000
--- a/docs/1_CodeConventions.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# Code Conventions
-
-
-- [Code Conventions](#code-conventions)
- - [filenames](#filenames)
- - [data-testid(s)](#data-testids)
- - [ts-ignore comments](#ts-ignore-comments)
- - [Composables](#composables)
-
-
-
-
-## filenames
-
-Files should be consistently named like this:
-| file content | filename |
-| ------------ | ----------------------------: |
-| Components | `YourComponent.vue` |
-| Pages | `YourPageName.page.vue` |
-| Layouts | `yourLayoutName.layout.vue` |
-| Composables | `yourComponent.composable.ts` |
-| Tests | `yourTestFile.unit.ts` |
-| Utils | `yourUtil.ts` |
-| Pinia stores | `yourFilename.ts` |
-| Vuex stores | `your-filename.ts` |
-
-
-**Near future**: The structure of this project will move from the old *Atomic Design* (= using molecules- and atoms- folders) to a more use-case-centeric approach.
-Details are documented here: [Vue 3 project structure](https://docs.dbildungscloud.de/x/oYAgDQ)
-
-**Far future**: Linter Rules to enforce the project structure as decided in Frontend Arc Group Meeting 2022-08-26.
-
-**Current status**: For the moment we started to break up the *Atomic Design* by introducing feature-centric folders. (e.g. ``src / components /share-course / ...``).
-
-
-## data-testid(s)
-
-Please use ``
`` in your HTML-code if you want to define a data-testid.
-
-* do not use uppercase-characters
-* only use one dash - right after data
-
-We also recommend to use **ref**s instead of data-testids. But if you do that, you need to be careful when removing them... as they could be used in the component-code AND in tests:
-
-* [VueJs - template refs](https://vuejs.org/guide/essentials/template-refs.html)
-* [VueTestUtils - ref](https://v1.test-utils.vuejs.org/api/#ref)
-
-Also look here: *Frontend Arc Group: Meeting Notes 2022-11-04*
-
-
-##
ts-ignore comments
-
-Everybody should try to avoid ``// @ts-ignore`` and try his/her best to define the types of variables in TypeScript files.
-
-Also look here: *Frontend Arc Group: Meeting Notes 2022-10-28*
-
-
-##
Composables
-
-Composables are a great way to make our code more reusable and to extract code from components. If you want to write a composable, consider using one of these well documented and well tested ones:
-[VueUse - Collection of Vue Composition Utilities](https://vueuse.org/)
-
-If you write a composable:
-
-* it should have the extension ``.composable.ts``
-* should be placed in your feature folder (see section "directory structure" above), if it is only used inside of your feature
-* should be placed in the global folder ``/ src / composables``, if it is used in multiple features
diff --git a/docs/2_ProjectStructure.md b/docs/2_ProjectStructure.md
deleted file mode 100644
index a6ac8141bd..0000000000
--- a/docs/2_ProjectStructure.md
+++ /dev/null
@@ -1,162 +0,0 @@
-# Project Structure
-
-
-## Description of the Structure
-
-The projects code is separated into building blocks.
-
-## What is a building-block?
-
-A **building-block** is a "container" were we place most of our applications logic and components. Each building-block is defined by an `index.ts (Barrel-File)` describing it's exported content (public API of a building-block) and a `type`.
-
-Utilizing linting rules and the index.ts we can ensure that each building-block only exposes files which are meant to be used application-wide. This way we achieve a strong separation of concern across the whole application.
-
-
-Our linting rule is based on the following concept:
-
-[Enforce Project Boundaries | Nx](https://nx.dev/core-features/enforce-project-boundaries)
-
-`Note: in above documentation **libraries** are equivalent to building-blocks and **tags** represent the types defined below.`
-
-## Types of building-blocks
-
-**Page**
-
-Contains a subpage of the application. Orchestrates Feature and UI building-blocks.
-
-`Example: page-dashboard`
-
-**Feature**
-
-Complex features with **stateful / smart components**. Usually specialized to fulfill specific roles in the App. Can also contain presentational components that are specialized for this feature.
-
-`Example: feature-calendar`
-
-**UI**
-
-**Stateless / presentational components** which get their data via props and emit events. Usually less specialized.
-
-`Example: ui-forms`
-
-**Data**
-
-State and API-access. Does not contain any visual components. They are the data-sources of all smart components.
-
-`Example: data-auth`
-
-**Util**
-
-Contains shared low-level code.
-
-`Example: util-form-validators`
-
-## Type: Page
-
-### What is it?
-
-A page building-block represents a **subpage** of the application. It contains the layout component and orchestrates feature and ui building blocks to create a subpage. It can not be imported into any other type of building-block. It is **only imported by the vue-router** and should be **lazy-loaded** if possible.
-
-### Naming Convention
-
-Placed in folder **page**
-
-### Can import types
-
-data, util, ui, feature
-
-## Type: Feature
-
-### What is it?
-
-A feature building-block contains a set of files that represent a business use case in an application.
-
-Most of the components of features are **stateful / smart components** that interact with data sources. This type also contains most of the UI logic, form validation code, etc.
-
-### Naming Convention
-
-Placed in folder **feature**
-
-### Can import types
-
-data, util, ui, feature
-
-
-## Type: UI
-
-### What is it?
-
-A ui building-block mainly contains **Stateless / presentational components** which are used all across the application. They don't have access to stores and do not use features in their templates. All data needed for components in ui building-blocks comes from props.
-
-### Naming Convention
-
-Placed in folder **ui**
-
-### Can import types
-
-util, other ui
-
-## Type: Data
-
-### What is it?
-
-A data building-block contains **stores and api-services**. It does not contain any view components. They serve as data-sources for feature and page building blocks.
-
-### Naming Convention
-
-Placed in folder **data**
-
-### Can import types
-
-util, other data
-
-## Type: Util
-
-### What is it?
-
-A utility building-block contains **low level code** used by many building-blocks. Often there is no framework-specific code and the building-block is simply a collection of types, utilities, pure functions, factories or composables.
-
-### Naming Convention
-
-Placed in folder **util**
-
-### Can import types
-
-other util
-
-# How to pick the correct type for my Task
-
-`To render this graph in VS-Code markdown preview install this extension: bierner.markdown-mermaid`
-
-```mermaid
-flowchart TD
- A[Your Task] --> B[Imagine the different requirements of the Task]
- B --> C{Do I need a new subpage}
- C -->|Yes| D(type: page)
- C -->|No| E{Do I need UI?}
- E --> |No| F{Do I need State?}
- E --> |Yes| G{Do I need State?}
- F -->|Yes| H(type: data)
- F -->|No| I(type: util)
- G -->|Yes| J(type: feature)
- G -->|No| K(type: ui)
- H --> L[Are all requirements of your task placed in a building-block?]
- I --> L
- J --> L
- K --> L
- D --> L
- L -->|Yes| M[Happy Coding!]
- L -->|No| O[You need an additional building-block]
- O --> B
- M -.-> P[Evaluate your choices as a part of your review and refactor when neccessary]
-
-```
-
-# Matrix of allowed imports
-
-| Allowed to Import ➡
It is ⬇ | page | feature | data | ui | util |
-| ------------------------------- | ---- | ------- | ---- | --- | ---- |
-| page | | ✔ | ✔ | ✔ | ✔ |
-| feature | | ✔ | ✔ | ✔ | ✔ |
-| data | | | ✔ | | ✔ |
-| ui | | | | ✔ | ✔ |
-| util | | | | | ✔ |
diff --git a/docs/3_GitConventions.md b/docs/3_GitConventions.md
deleted file mode 100644
index 64367abbd4..0000000000
--- a/docs/3_GitConventions.md
+++ /dev/null
@@ -1,42 +0,0 @@
-#
Git Conventions
-
-Each change should be done in a Ticket (no matter how small).
-
-We use a [Feature Branch model](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow). Start a branch from main and make a PR to main.
-
-**Branch naming:**
-
-`{{ PROJECT_ABBREVIATION }}-{{ NUMBER }}-word1-word2-word2`
-
-e.g.: ``BC-1234-course-copy``
-
-We try to keep branch names small. The Ticket Number should be in Uppercase (e.g BC-1234) but the namespace should be in lowercase. It should stay below 64 letters.
-
-##
Pull Requests
-
-Pull Requests must contain a relevant description (template provides useful information, when creating the PR).
-
-In case of UI changes also put a screenshot and talk to UX if thats fine like it is.
-All Pull Requests Criterias (as defined in deployment pipeline) must be green before merge,
-e.g. 1 approving review, unit tests or QA checkbox in PR template.
-
-We merge by squash strategy. The squashed commit subject should start with a ticket number and end with a PR number. Write commit messages in imperative and active.
-
-**Example:**
-
-```Text
-BC-1993 - lesson lernstore and geogebra copy (#3532)
-
-In order to make sure developers in the future can find out why changes have been made,
-we would like some descriptive text here that explains what we did and why.
-
-- change some important things
-- change some other things
-- refactor some existing things
-
-# We dont need to mention tests, changes that didnt make it to main, linter, or other fixups
-# only leave lines that are relevant changes compared to main
-# comments like this will not actually show up in the git history
-```
-
-**Note for working with Windows:** We strongly recommend to let git translate line endings. Please set `git config --global --add core.autocrlf` input when working with windows.
diff --git a/docs/4_HowTo.md b/docs/4_HowTo.md
deleted file mode 100644
index b02161e420..0000000000
--- a/docs/4_HowTo.md
+++ /dev/null
@@ -1,169 +0,0 @@
-# HowTo
-
-Collection of instructions on how to do certain things:
-
-
-* [Feature Flags](#FeatureFlags)
-* [Using generated API and it's types](#UsinggeneratedAPIanditstypes)
-* [User-Permissions on Pages](#User-PermissionsonPages)
-* [Exception handling](#Exceptionhandling)
-* [inject - fallback throwing an error](#inject-fallbackthrowinganerror)
-
-
-
-
-##
Feature Flags
-
-If there is a new functionality that should only be available on certain systems, we introduce new FEATURE-Flags into the SchulCloud-Backend and into the dof-repository, that contains the configuration for all our instances.
-
-Our Vue-Frontend requests all FEATURE-flags and provides global access to them by using this code (example):
-
-```TypeScript
-import { envConfigModule } from "@/store";
-if (envConfigModule.getEnv.FEATURE_COPY_SERVICE_ENABLED) {
- ...
-}
-```
-
-##
Using generated API and it's types
-
-We are using a generator script to create classes to access the Schulcloud-Backend-API - V3 (so Legacy-Backend endpoints (aka V1) are not covered).
-These generated classes and methods internally use axios to request data and use generated types - both for the input to the methods and for the returned types.
-
-> **HINT**
->
-> Please use the generated types in your stores and do not redefine the same types. This way consistency between Server and Api-Access stays stable.
-
-
-### Regenerating the clients
-
-Only if the server-api or the filestore-api has changed, you need to regenerate them using the following npm-scripts:
-
-For generating the files to access the **server-api** please use:
-
-```shell
-npm run generate-client:server
-```
-
-The same is implemented for generating the backend-api to our filestore-backend.
-
-For generating the files to access the **filestore-api** please use:
-
-```shell
-npm run generate-client:filestorage
-```
-
-> **Hint**
->
-> For regenerating the clients you need an up-to-date running backend-server running in your environment.
-
-### Using the generated api
-
-The generated APIs can easily be used. Examples can be seen in any current store-implementation - like here:
-
-```TypeScript
-src/store/share-course.ts:
-
-import {
- ShareTokenApiFactory,
- ShareTokenApiInterface,
- ShareTokenBodyParams,
- ShareTokenBodyParamsParentTypeEnum,
- ShareTokenResponse,
-} from "../serverApi/v3/api";
-
-...
-
-export default class ShareCourseModule extends VuexModule {
- ...
- private get shareApi(): ShareTokenApiInterface {
- return ShareTokenApiFactory(undefined, "v3", $axios);
- }
-
- @Action
- async createShareUrl(
- payload: SharePayload
- ): Promise
{
- const shareTokenPayload: ShareTokenBodyParams = {
- parentType: ShareTokenBodyParamsParentTypeEnum.Courses,
- parentId: this.courseId,
- expiresInDays: payload.hasExpiryDate ? 21 : null,
- schoolExclusive: payload.isSchoolInternal,
- };
- ...
- const shareTokenResult =
- await this.shareApi.shareTokenControllerCreateShareToken(
- shareTokenPayload
- );
- ...
- }
- ...
-}
-
-```
-
-
-
-## User-Permissions on Pages
-
-The permissions are controlled by `createPermissionGuard` middleware method that receives two parameters. The first parameter should contain an array of the `userPermission` that is required to reach the page. The second parameter is an optional fallback route. If the second parameter isn't provided and the user has no permission to reach the page, an error page `(401)` is shown.
-
-```Typescript
-// src/router/routes.ts
-
-// with a fallback route
-{
- path: "/your/route",
- component: () => import("../pages/your.page.vue"),
- name: "yourRouteName",
- beforeEnter: createPermissionGuard(["ADMIN_VIEW"], "/yourFallBackRoute"),
-},
-
-// without a fallback,
-// it shows a '401' file if the user doesn't have permissions
-{
- path: "/your/route",
- component: () => import("../pages/your.page.vue"),
- name: "yourRouteName",
- beforeEnter: createPermissionGuard(["ADMIN_VIEW", "SCHOOL_EDIT"]),
-},
-```
-
-## Exception handling
-
-**useApplicationError** is a composable providing a typed factory function for creating application errors.
-A global error handler for putting application errors takes those and puts them into a store and a global error page will display them.
-
-Exceptions should be thrown using them - like this:
-
-```TypeScript
-// src/pages/user-migration/UserMigration.page.vue
-import { useApplicationError } from "@/composables/application-error.composable";
-
-const { createApplicationError } = useApplicationError();
-throw createApplicationError(HttpStatusCode.BadRequest);
-```
-
-```TypeScript
-// src/router/guards/permission.guard.ts
-import { useApplicationError } from "@/composables/application-error.composable";
-import { applicationErrorModule } from "@/store";
-
-const { createApplicationError } = useApplicationError();
-applicationErrorModule.setError(createApplicationError(401));
-```
-
-*Also look here: [Meeting Notes 2022-11-25](https://docs.dbildungscloud.de/x/joL4DQ)*
-
-## inject - fallback throwing an error
-
-> We want to provide a simple factory function that produces a unique, identifiable error, if an inject fails and we want to avoid adding code to your TypeScript-components only to prevent linter errors.
-> The topic will be implemented with this ticket: [Jira - BC-2813](https://ticketsystem.dbildungscloud.de/browse/BC-2813). It contains a lot of details on that issue.
->
-> ... Details should be added here. soon...
-
-*Also look here: Frontend Arc Group: Meeting Notes 2022-12-02*
-
diff --git a/docs/5_WritingTests.md b/docs/5_WritingTests.md
deleted file mode 100644
index c2152a8219..0000000000
--- a/docs/5_WritingTests.md
+++ /dev/null
@@ -1,359 +0,0 @@
-# Writing Tests
-
-How to write valuable, reliable tests, that are easy to maintain.
-
-## Basics
-
-Writing good tests that cover all aspects of your code, leads to:
-
-- **confidence**: to refactor your code
-- **higher code quality**: as you review your code and identify problems when writing tests
-- **well documented code**: as your tests describe how your code works
-
-and by that to:
-
-- **developer happiness** :-)
-
-### Unit-Tests vs. Component-Tests
-
-#### Unit-Tests
-
-Unit-Tests are **WhiteBox-Tests**. So they may use knowledge of internals of the code. They are well suited for testing e.g. **composables** and **stores**.
-
-#### Component-Tests
-
-Component-Tests are **BlackBox-Tests**. So they are not allowed to use any knowledge of the internals of the component.
-They ensure the stability of the **public interface** of the component (aka its methods, props, events etc.).
-The enable us to **refactor** the internals of our components later on.
-
-### Positive & negative Tests
-
-- **positive tests** test the default cases of your code = **how it should work**
-- **negative tests** test **error-cases** or **exception**-behaviour
-- you need to write both to ensure your component works correctly
-- think of edge-cases that might break your component e.g. when providing input to the component:
- - **numbers**: high numbers, negative numbers, float<->integer, at the edge of a range that is expected...
- - **dates**: none existing dates e.g. 30th February 2023, far away future,...
- - **strings**: umlauts, url-special-characters (?, &, =, \/\/: ), very long strings for names, long strings without linebreaks
- - **totally incorrect data**: e.g. giving a string instead of a number
-
-### Use Vue-Test-Utils
-
-For testing our Vue-Components we use the **Vue Test Utils**. Vue Test Utils is a library that provides methods to help you write tests for your Vue components. It provides methods to mount, shallow mount, and render components, as well as methods to simulate events and find elements in the rendered output.
-
-Some functionality it provides:
-
-- **mount()**: create a wrapper around the component and instantiate it
-- **shallowMount()**: create a shallow wrapper of the component being tested with childcomponents being mocked
-- **setMethods()**: mock function on the component
-- **setProps()**: set a specific set of props on the component
-- **findComponent()**: finds a component by it's class, name or ref
-- **findAllComponents()**: finds all components by it's class, name or ref
-- **[find() / findAll()](https://v1.test-utils.vuejs.org/api/wrapper/#find)**: search for html elements using html-selectors
- - **deprecated for finding Components**
- - use findComponent() or findAllComponents() instead
-- **setData()**: set specific data on the component
-- **trigger()** + **emit()**: test events and the flow of data
-
-We think the **Vue Test Utils-documentation** is a valuable resource for learning how to test Vue-Components and a very good starting point on how to test certain aspects of your component. Please have a look at [https://test-utils.vuejs.org/guide](https://test-utils.vuejs.org/guide)
-
-### Use TypeScript
-
-Use TypeScript for your components and for your unit-tests. This way many errors can be prevented early on, as you can detect them already in your IDE.
-
-### Name your tests like your components
-
-Tests should be named after their Component using **.unit.ts** as the extension:
-
-```JavaScript
-HelloWorld.vue
-HelloWorld.unit.ts
-```
-
-### Structure your tests using (multiple) "describe"-blocks
-
-Especially in large test-files it is very helpful for the reader to have a tree-like structure grouping the tests. So use describe blocks to group tests that are related to the same aspect of your code/the functionality.
-
-1. describe block that contains the filename in the root-level of the test-file
-2. sub-describe-blocks for groups of tests focussing the same aspects of your code
-
-*Example:*
-
-```TypeScript
-describe('@components/share/ImportModal', () => {
- describe('when action button is clicked', () => {
- ...
- });
-
- ...
-
- describe("when backend returns an error", () => {
-
- });
-});
-```
-
-Example taken from here [Vue NYC - Component Tests with Vue.js - Matt O'Connell](https://www.youtube.com/watch?v=OIpfWTThrK8)
-```TypeScript
-describe('@components/something/AddButton', () => {
- describe(':props', () => {
- it(':label - should render a button with the passed-in label text', () => { ... })
- });
-
- ...
-
- describe("@events", () => {
- it('@add - should emit an "add" event when the button is clicked', () => { ... })
- });
-});
-```
-
-**Hint**: *maybe you should extract functionality from your component if this is needed e.g. to find a certain test in your file*
-
-### Name the test like a sentence "it should..."
-
-There is a reason we use the it-alias for writing our code and not the test-method: we want to describe the aspect that is tested in a natural sentence. That's why it is best practice to start your test with: it('should ...');
-
-*Example:*
-
-```TypeScript
-Bad:
-it('name changes on button click')
-...
-
-Good:
-it('should display the info text', ... );
-it('should not render migration start button', ... );
-it('should return the translation', ... );
-```
-
-### data-testids
-
-Data-testids are attributes to HTML-elements that are solely used to enable tests to find and check a certain aspect of that tag (often to check the contained text against some expected value).
-
-We decided to unify the way data-testid's should be named in Frontend Arch Group: [Meeting 2022-11-04](https://docs.dbildungscloud.de/x/mYHADQ)
-
-Please use ```` in your HTML-code if you want to define a data-testid.
-
-- do not use uppercase-characters
-- only use one dash - right after data
-
-You can later on check this using:
-
-```TypeScript
-// CopyResultModal.unit.ts
-expect(
- wrapper.find('[data-testid="copy-result-notifications"]').text()
-).toContain(
- wrapper.vm.$i18n.t("components.molecules.copyResult.fileCopy.error")
-);
-```
-
-We also recommend to use refs instead of data-testids. But if you do that you ensure not to remove them once they are in the code... as they can be used in the component-code and for testing:
-
-- [VueJs - template refs](https://vuejs.org/guide/essentials/template-refs.html)
-- [VueTestUtils - ref](https://v1.test-utils.vuejs.org/api/#ref)
-
-### Setup-methods
-
-Separate your setup from your actual tests: If you need a more complex setup to test something - write a scope method called "setup" for it. Write it in a reusable and configurable way, in order to reuse most of it in several groups of tests. You will get small and easily readable tests and no redudant setup-code inside your tests that contains small differences that are hard to detect.
-
-## Testing
-
-### Events
-
-Use the trigger()-method to simulate a events
-[Testing Key, Mouse and other DOM events](https://v1.test-utils.vuejs.org/guides/#testing-key-mouse-and-other-dom-events)
-
-- **Mouse-Click**: [VueTestUtils - trigger events](https://v1.test-utils.vuejs.org/guides/#trigger-events)
-- **Keyboard-Input**: [VueTestUtils - keyboard example](https://v1.test-utils.vuejs.org/guides/#keyboard-example)
-- **Drag & Drop**: trigger the events (e.g. dragstart, drop) and check for emitted events as reaction to that
-- **Event from a child component**: [VueTestUtils - emitting from child component](https://v1.test-utils.vuejs.org/guides/#emitting-event-from-child-component)
-
-### Testing Asynchronous Behavior
-
-You can test asynchronous behavior by using ***Vue.nextTick()***:
-
-```TypeScript
-await Vue.nextTick();
-...
-```
-
-OR by ***trigger***ing an effect and ***await***ing this effect to take place:
-
-```TypeScript
-const btnNext = wrapper.find(`[data-testid="dialog-next"]`);
-await btnNext.trigger("click");
-...
-```
-
-**see also**: [VueTestUtils - Testing Asynchronous Behavior](https://v1.test-utils.vuejs.org/guides/#testing-asynchronous-behavior)
-
-### Exceptions
-
-```TypeScript
-await expect(() => copyModule.copy(payload)).rejects.toThrow(
- `CopyProcess unknown type: ${payload.type}`
-);
-```
-
-### console.error
-
-```TypeScript
-// UserMigration.page.unit.ts
-const consoleErrorSpy = jest
- .spyOn(console, "error")
- .mockImplementation();
-
-...
-
-expect(consoleErrorSpy).toHaveBeenCalledWith(
- expect.any(ApplicationError)
-);
-consoleErrorSpy.mockRestore();
-```
-
-### Testing Composables
-
-- [VueTestUtils - Testing composables](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#testing-composables)
-
-## Mocking
-
-Replaces methods, instances of classes (e.g. stores) with some functionality, that e.g. simply returns a value you want to use in your test. By mocking you can easily simulate certain scenarios like failing requests or certain return values from any "external" (as in "not part of the code i am currently testing") functionality.
-Jest provides very helpful methods for that.
-Examples from our codebase:
-
-```TypeScript
-const mock = jest.fn().mockReturnValue(expectedTranslation);
-```
-
-```TypeScript
-copyModuleMock.copyByShareToken = jest.fn()
- .mockResolvedValue(copyResults);
-```
-
-They can easily be tested like this:
-
-```TypeScript
-expect(copyModuleMock.copyByShareToken).toHaveBeenCalled();
-```
-
-Or more specific like this:
-
-```TypeScript
-expect(addFileMetaDataSpy).toHaveBeenCalledWith(
- expect.objectContaining
({ size: 2 } as FileMetaListResponse)
-);
-```
-
-See also here: [VueTestUtils mount - mocks and stubs are now in global](https://test-utils.vuejs.org/migration/)
-
-
-### Mocking injections
-
-- [Vue.js - Mocking injections](https://v1.test-utils.vuejs.org/guides/#mocking-injections)
-- [VueTestUtils - provide / inject](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#provide-inject)
-
-### Mocking Vuex-Store
-
-#### Mocking a vuex-store in a component:
-
-Example file: `src/components/administration/AdminMigrationSection.unit.ts`
-
-```TypeScript
-import { createModuleMocks } from "@/utils/mock-store-module";
-import YourModule from "@/store/YourModule";
-
-let yourModule: jest.Mocked;
-
-schoolsModule = createModuleMocks(YourModule, {
- yourMethodName: {
- ...
- },
- ...yourGetters,
-}) as jest.Mocked;
-
-
-mount(YourComponentToBeTested, {
- ...createComponentMocks({
- ...
- }),
- provide: {
- yourModule,
- },
-});
-
-expect(yourModule.).toHaveBeenCalledWith(...);
-```
-
-#### Testing a store:
-
-```TypeScript
-import YourModule from "./your-module";
-
-const yourModule = new YourModule({});
-
-...
-
-// using `jest.spyOn()`
-it("should call something", () => {
- const yourActionNameMock = jest.spyOn(yourModule, "yourActionName");
- yourModule.yourActionName();
- expect(yourActionNameMock).toHaveBeenCalled();
-});
-
-// or using a method directly
-it("should set something", () => {
- yourModule.setLoading(true);
- expect(yourModule.getLoading).toBe(true);
-});
-```
-
-### Mocking Pinia-Stores
-
-***{{ tbd }}** (when Pinia-Stores are enabled for the project)*
-
-### Mocking Composables
-
-Sometimes - if a composable is simple and does not create sideeffects - it is okay to use it in the tests and avoid mocking it.
-
-That's beneficial as it let's us stick to the BlackBox-Idea: we should not know what the component is using internally.
-
-If you need to mock a composable, you can simple do this like in the following example. You only have to ensure to return everything the composable returns... but mocked versions of it.
-
-```TypeScript
-...
-jest.spyOn(ourExampleComposable, "useExample").mockReturnValue({
- // return mocks of what the composable would have returned
-});
-...
-```
-
-
-## Components that are hard to test
-
-If you ever get into trouble to write good tests for your compents or code in general - this might be an indicator, that **maybe your code is not structured good enough**.
-
-Consider:
-
-- spliting your component into smaller sub-components with a small API
-- extracting functionality into one or mutliple composables
-- using an existing composable (from VueUse or an existing one in the project)
-- using an existing vuetify-component instead of writing it all yourself
-- reshaping the communication workflow (parameters, events, inject/provide, stores, composables)
-- (replacing a Vuex-store with a Pinia-store)
-
-For more details on how to write good components and how to split your components: have a look at this great article of Olli: (tbd)
-
-
-## End-To-End-Tests
-
-(aka Integration/Acceptance/System-Tests)
-
-End-to-End-Tests are developed in a seperate repository [end-to-end-tests](https://github.com/hpi-schul-cloud/end-to-end-tests)
-
-[Documentation of e2e tests](https://docs.dbildungscloud.de/x/tAgrCg)
-
-## Code-Coverage
-
-For monitoring our code-coverage we are using [Codacy](https://www.codacy.com). The current status can be seen on this [Dashboard](https://app.codacy.com/gh/hpi-schul-cloud/nuxt-client/dashboard/).
diff --git a/docs/6_Accessibility.md b/docs/6_Accessibility.md
deleted file mode 100644
index 6aa1171003..0000000000
--- a/docs/6_Accessibility.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Accessibility (A11y)
-
-We want to make sure that our product can be used by anyone.
-This includes people with disabilities as well as people that have a slow connection, outdated or broken hardware or people that just have an unfavorable environment.
-
-## W3C Web Accessibility Initiative (WAI)
-
-The WAI develops strategies, standards, resources to make the web accessibile.
-
-An introduction to Accessibility can be found here: [Introduction to Web Accessibilty](https://www.w3.org/WAI/fundamentals/accessibility-intro/)
-
-
-The WAI ARIA is the Acessible Rich Internet Applications suite of web standards. It makes Web content and Web applications more accessible with adding attributes to identify features for user interaction and enable e.g. keyboard users to move among regions.
-
-A recommended approach using WAI-ARIA roles, states and properties can be found here: [ARIA Authoring Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg/)
-
-## Vuetify and Vue
-
-We want to use Vuetfiy Components in our project. They provide key interaction for all mouse-based-actions and utilize HTML5 semantic elements where applicable.
-
-See: [Vuetify A11y](https://vuetifyjs.com/en/features/accessibility/)
-
-There is also a Accessibility Chapter in the Best Practices of the Vue Docs: [Vue A11y](https://vuejs.org/guide/best-practices/accessibility.html)
-
-
-// TODO: link to good tutorial on how to test a11n
-// REMINDER: If we establish special a11n-components and/or tools - they should be described here
diff --git a/docs/7_Colors.md b/docs/7_Colors.md
deleted file mode 100644
index 2445487257..0000000000
--- a/docs/7_Colors.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Colors
-
-You can find our custom defined theme colors under `/src/themes/base-vuetify.options.js` and their overwrites per theme in `/src/themes//vuetify.options.js`.
-
-The colors vuetify provides you can find [here](https://v2.vuetifyjs.com/en/styles/colors/)
-
-## Usage
-
-### Color Classes
-
-All colors defined by vuetify or in our vuetify options generate css classes you can use. To aplly a color variant like lighten1, add a second class like "grey lighten-1".
-
-#### Examples
-
-Using a color from vuetify's color palette:
-
-```HTML
-
- Blue background
-
-```
-
-Using a color defined in our vuetify options as text color:
-
-```HTML
-
- Blue background
-
-```
-
-To use a variant of a color, you have to add a second class with the name of the variant:
-
-```HTML
-
- Blue background
-
-```
-
-### Use Colors in (S)CSS
-
-For colors defined in our vuetify options, vuetify generates css variables.
-
-#### Example
-
-```SCSS
-.alert {
- background-color: var(--v-secondary-darken1);
- color: var(--v-white-base);
-}
-```
-
-Colors from vuetify's colors palette (as of now) do not get generated css variables. You will need to access them with map-get.
-
-#### Example
-
-```SCSS
-.alert {
- background-color: color: map-get($grey, base);;
- color: color: map-get($blue, lighten-3);;
-}
-```
-
-## Definition
-
-You can define more custom colors in our vuetify options like this:
-
-```JS
-...
-"icon-btn": {
- base: colors.grey.darken3,
-},
-"beta-task": {
- base: "#196C9E",
-},
-...
-```
-
-As of now you can only use hex values without the alpha property. If a color is only meant for one theme only, please define the color in the relating theme file for our vuetify options.
-
-### Rules
-
-- Do not overwrite vuetify colors
-- Use a semantic name to represent the use case
-- Prefer usage via map-get over new color definition, unless you introduce a new color
-- Either define style in template or in scss
diff --git a/docs/8_IdentifyingAndResolvingCircularDependencies.md b/docs/8_IdentifyingAndResolvingCircularDependencies.md
deleted file mode 100644
index 17b316ad8d..0000000000
--- a/docs/8_IdentifyingAndResolvingCircularDependencies.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Identifying and Resolving Circular Dependencies
-
-## What is a circular dependency?
-Circular depencies are a common issue when working with barrel-files (index.ts).
-
-Let's look at a common dependency pattern:
-
-![Circular Import](./assets/circular_dependency_1.png)
-
-
-In this example there are two **Building-Blocks** (e.g. folders that have a barrel file) which depend on each other. Using the SharedComponent in both Building-Blocks will result in a circular dependency.
-
-That basically means that the compiler can not resolve the order to load the Building-Blocks which causes an error.
-
-
-
-## Resolving Circular Dependencies
-
-The basic gist is: **break the circle** and separate the shared dependency in a separate module.
-
-![Fixed Circular Import](./assets/circular_dependency_2.png)
-
-In this configuration the compiler can find an order to resolve the building-blocks correctly.
-
-
-
-## How to identify Circular Dependencies in Vue
-
-I recreated the first example error in Vue. When Vue tries to render ComponentA I see the following Error in the console:
-
-![Error Message](./assets/circular_dependency_3.png)
-
-This can be quite hard to decipher on a first glance but it contains all the info we need to identify the root cause of the circular dependency.
-
-![Error Message Explained](./assets/circular_dependency_4.png)
-
-Based on the info from the message we can learn that ComponentB "closed the circle" by importing SharedComponent. From there we can trace back to see where SharedComponent is exposed and why it depends on ComponentB. In this case it is because they are both imported in ComponentA.
-
-Keep in mind that the circular dependency can involve multiple building-blocks.
-
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index 46c733ee8f..0000000000
--- a/docs/README.md
+++ /dev/null
@@ -1,32 +0,0 @@
-**Schulcloud-Verbund-Software - Frontend**
-
-# Documentation
-
-
-
-**[Getting Started](./0_GettingStarted.md)**
-
-First steps to get your Frontend-Server up and running.
-
-
-**[Code Conventions](./1_CodeConventions.md)**
-
-Code Conventions defined for the project like **file naming**, **directory structure** , **data-testid(s)** and others...
-
-
-**[Git Conventions](./2_GitConventions.md)**
-
-Git Conventions on how to name your **branches** and how to create and name **PullRequests**.
-
-**[How To...](./3_HowTo.md)**
-
-Collection of information on how to use **Feature Flags**, the **generated API**, **user-permissions on pages**, **exception handling** and more.
-
-**[Writing Tests](./4_WritingTests.md)**
-
-Detailed description on **how to write tests**.
-
-**[Accessibility](./5_Accessibility.md)**
-
-Collection of information and links on how we want to achieve a great accessibility.
-
diff --git a/docs/assets/circular_dependency_1.png b/docs/assets/circular_dependency_1.png
deleted file mode 100644
index caeb688d56..0000000000
Binary files a/docs/assets/circular_dependency_1.png and /dev/null differ
diff --git a/docs/assets/circular_dependency_2.png b/docs/assets/circular_dependency_2.png
deleted file mode 100644
index ad6a68bfa3..0000000000
Binary files a/docs/assets/circular_dependency_2.png and /dev/null differ
diff --git a/docs/assets/circular_dependency_3.png b/docs/assets/circular_dependency_3.png
deleted file mode 100644
index 9ed7a8aa09..0000000000
Binary files a/docs/assets/circular_dependency_3.png and /dev/null differ
diff --git a/docs/assets/circular_dependency_4.png b/docs/assets/circular_dependency_4.png
deleted file mode 100644
index 9957c4033f..0000000000
Binary files a/docs/assets/circular_dependency_4.png and /dev/null differ
diff --git a/package-lock.json b/package-lock.json
index c4190beb75..e672faf9d8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,10 +12,10 @@
"@lumieducation/h5p-webcomponents": "^9.2.2",
"@mdi/js": "^7.2.96",
"@types/vuelidate": "^0.7.18",
- "@vueuse/components": "^10.3.0",
+ "@vueuse/components": "^10.5.0",
"@vueuse/core": "^10.4.1",
"axios": "^1.5.0",
- "dayjs": "^1.11.9",
+ "dayjs": "^1.11.10",
"flush-promises": "^1.0.2",
"katex": "^0.12.0",
"kjua": "^0.9.0",
@@ -39,7 +39,7 @@
"devDependencies": {
"@golevelup/ts-jest": "^0.4.0",
"@intlify/vue-i18n-loader": "^4.0.0",
- "@openapitools/openapi-generator-cli": "^2.5.2",
+ "@openapitools/openapi-generator-cli": "^2.7.0",
"@types/jest": "^27.5.2",
"@types/object-hash": "^2.2.1",
"@typescript-eslint/eslint-plugin": "^5.4.0",
@@ -2983,15 +2983,15 @@
"integrity": "sha512-paR9M9ZT7rKbh2boksNUynuSZMHhqRYnEZOm/KrZTjQ4/FzyhjLHuvw/8XYzP+E7fS4+/Ms/82EN1pl/OFsiIA=="
},
"node_modules/@nestjs/axios": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.0.8.tgz",
- "integrity": "sha512-oJyfR9/h9tVk776il0829xyj3b2e81yTu6HjPraxynwNtMNGqZBHHmAQL24yMB3tVbBM0RvG3eUXH8+pRCGwlg==",
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.1.0.tgz",
+ "integrity": "sha512-b2TT2X6BFbnNoeteiaxCIiHaFcSbVW+S5yygYqiIq5i6H77yIU3IVuLdpQkHq8/EqOWFwMopLN8jdkUT71Am9w==",
"dev": true,
"dependencies": {
"axios": "0.27.2"
},
"peerDependencies": {
- "@nestjs/common": "^7.0.0 || ^8.0.0",
+ "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0",
"reflect-metadata": "^0.1.12",
"rxjs": "^6.0.0 || ^7.0.0"
}
@@ -3007,23 +3007,21 @@
}
},
"node_modules/@nestjs/common": {
- "version": "8.4.7",
- "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-8.4.7.tgz",
- "integrity": "sha512-m/YsbcBal+gA5CFrDpqXqsSfylo+DIQrkFY3qhVIltsYRfu8ct8J9pqsTO6OPf3mvqdOpFGrV5sBjoyAzOBvsw==",
+ "version": "9.3.11",
+ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.3.11.tgz",
+ "integrity": "sha512-IFZ2G/5UKWC2Uo7tJ4SxGed2+aiA+sJyWeWsGTogKVDhq90oxVBToh+uCDeI31HNUpqYGoWmkletfty42zUd8A==",
"dev": true,
- "peer": true,
"dependencies": {
- "axios": "0.27.2",
"iterare": "1.2.1",
- "tslib": "2.4.0",
- "uuid": "8.3.2"
+ "tslib": "2.5.0",
+ "uid": "2.0.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nest"
},
"peerDependencies": {
- "cache-manager": "*",
+ "cache-manager": "<=5",
"class-transformer": "*",
"class-validator": "*",
"reflect-metadata": "^0.1.12",
@@ -3041,23 +3039,11 @@
}
}
},
- "node_modules/@nestjs/common/node_modules/axios": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
- "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "follow-redirects": "^1.14.9",
- "form-data": "^4.0.0"
- }
- },
"node_modules/@nestjs/common/node_modules/tslib": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
- "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
- "dev": true,
- "peer": true
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
+ "dev": true
},
"node_modules/@nicolo-ribaudo/semver-v6": {
"version": "6.3.3",
@@ -3204,13 +3190,13 @@
}
},
"node_modules/@openapitools/openapi-generator-cli": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.6.0.tgz",
- "integrity": "sha512-M/aOpR7G+Y1nMf+ofuar8pGszajgfhs1aSPSijkcr2tHTxKAI3sA3YYcOGbszxaNRKFyvOcDq+KP9pcJvKoCHg==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.7.0.tgz",
+ "integrity": "sha512-ieEpHTA/KsDz7ANw03lLPYyjdedDEXYEyYoGBRWdduqXWSX65CJtttjqa8ZaB1mNmIjMtchUHwAYQmTLVQ8HYg==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
- "@nestjs/axios": "0.0.8",
+ "@nestjs/axios": "0.1.0",
"@nestjs/common": "9.3.11",
"@nestjs/core": "9.3.11",
"@nuxtjs/opencollective": "0.3.2",
@@ -3238,45 +3224,6 @@
"url": "https://opencollective.com/openapi_generator"
}
},
- "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/common": {
- "version": "9.3.11",
- "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.3.11.tgz",
- "integrity": "sha512-IFZ2G/5UKWC2Uo7tJ4SxGed2+aiA+sJyWeWsGTogKVDhq90oxVBToh+uCDeI31HNUpqYGoWmkletfty42zUd8A==",
- "dev": true,
- "dependencies": {
- "iterare": "1.2.1",
- "tslib": "2.5.0",
- "uid": "2.0.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nest"
- },
- "peerDependencies": {
- "cache-manager": "<=5",
- "class-transformer": "*",
- "class-validator": "*",
- "reflect-metadata": "^0.1.12",
- "rxjs": "^7.1.0"
- },
- "peerDependenciesMeta": {
- "cache-manager": {
- "optional": true
- },
- "class-transformer": {
- "optional": true
- },
- "class-validator": {
- "optional": true
- }
- }
- },
- "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/common/node_modules/tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
- "dev": true
- },
"node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/core": {
"version": "9.3.11",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.3.11.tgz",
@@ -3913,9 +3860,9 @@
}
},
"node_modules/@types/web-bluetooth": {
- "version": "0.0.17",
- "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz",
- "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA=="
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.18.tgz",
+ "integrity": "sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw=="
},
"node_modules/@types/webpack-env": {
"version": "1.18.0",
@@ -4987,9 +4934,9 @@
},
"node_modules/@vue/vue-loader-v15": {
"name": "vue-loader",
- "version": "15.10.1",
- "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.1.tgz",
- "integrity": "sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==",
+ "version": "15.10.2",
+ "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.2.tgz",
+ "integrity": "sha512-ndeSe/8KQc/nlA7TJ+OBhv2qalmj1s+uBs7yHDRFaAXscFTApBzY9F1jES3bautmgWjDlDct0fw8rPuySDLwxw==",
"dev": true,
"dependencies": {
"@vue/component-compiler-utils": "^3.1.0",
@@ -5006,6 +4953,9 @@
"cache-loader": {
"optional": true
},
+ "prettier": {
+ "optional": true
+ },
"vue-template-compiler": {
"optional": true
}
@@ -5059,41 +5009,19 @@
"dev": true
},
"node_modules/@vueuse/components": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.3.0.tgz",
- "integrity": "sha512-EeZz3kjmJI7bH7JSxxMlLyk21LGl6GQjXfpl2n/GiI9QSJi+BVzIra5kEty5eM8McwAanx3e/HnK4drYTgFOWw==",
- "dependencies": {
- "@vueuse/core": "10.3.0",
- "@vueuse/shared": "10.3.0",
- "vue-demi": ">=0.14.5"
- }
- },
- "node_modules/@vueuse/components/node_modules/@vueuse/core": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.3.0.tgz",
- "integrity": "sha512-BEM5yxcFKb5btFjTSAFjTu5jmwoW66fyV9uJIP4wUXXU8aR5Hl44gndaaXp7dC5HSObmgbnR2RN+Un1p68Mf5Q==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.5.0.tgz",
+ "integrity": "sha512-zWQZ8zkNBvX++VHfyiUaQ4otb+4PWI8679GR8FvdrNnj+01LXnqvrkyKd8yTCMJ9nHqwRRTJikS5fu4Zspn9DQ==",
"dependencies": {
- "@types/web-bluetooth": "^0.0.17",
- "@vueuse/metadata": "10.3.0",
- "@vueuse/shared": "10.3.0",
- "vue-demi": ">=0.14.5"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/@vueuse/components/node_modules/@vueuse/metadata": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.3.0.tgz",
- "integrity": "sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw==",
- "funding": {
- "url": "https://github.com/sponsors/antfu"
+ "@vueuse/core": "10.5.0",
+ "@vueuse/shared": "10.5.0",
+ "vue-demi": ">=0.14.6"
}
},
"node_modules/@vueuse/components/node_modules/vue-demi": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
- "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@@ -5116,34 +5044,23 @@
}
},
"node_modules/@vueuse/core": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.4.1.tgz",
- "integrity": "sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.5.0.tgz",
+ "integrity": "sha512-z/tI2eSvxwLRjOhDm0h/SXAjNm8N5ld6/SC/JQs6o6kpJ6Ya50LnEL8g5hoYu005i28L0zqB5L5yAl8Jl26K3A==",
"dependencies": {
- "@types/web-bluetooth": "^0.0.17",
- "@vueuse/metadata": "10.4.1",
- "@vueuse/shared": "10.4.1",
- "vue-demi": ">=0.14.5"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/@vueuse/core/node_modules/@vueuse/shared": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.4.1.tgz",
- "integrity": "sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==",
- "dependencies": {
- "vue-demi": ">=0.14.5"
+ "@types/web-bluetooth": "^0.0.18",
+ "@vueuse/metadata": "10.5.0",
+ "@vueuse/shared": "10.5.0",
+ "vue-demi": ">=0.14.6"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/core/node_modules/vue-demi": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
- "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@@ -5166,28 +5083,28 @@
}
},
"node_modules/@vueuse/metadata": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.4.1.tgz",
- "integrity": "sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.5.0.tgz",
+ "integrity": "sha512-fEbElR+MaIYyCkeM0SzWkdoMtOpIwO72x8WsZHRE7IggiOlILttqttM69AS13nrDxosnDBYdyy3C5mR1LCxHsw==",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.3.0.tgz",
- "integrity": "sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.5.0.tgz",
+ "integrity": "sha512-18iyxbbHYLst9MqU1X1QNdMHIjks6wC7XTVf0KNOv5es/Ms6gjVFCAAWTVP2JStuGqydg3DT+ExpFORUEi9yhg==",
"dependencies": {
- "vue-demi": ">=0.14.5"
+ "vue-demi": ">=0.14.6"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared/node_modules/vue-demi": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
- "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@@ -7564,9 +7481,9 @@
}
},
"node_modules/dayjs": {
- "version": "1.11.9",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz",
- "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
+ "version": "1.11.10",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
},
"node_modules/de-indent": {
"version": "1.0.2",
@@ -14834,9 +14751,15 @@
"dev": true
},
"node_modules/nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -15777,9 +15700,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
@@ -15788,10 +15711,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
- "nanoid": "^3.3.4",
+ "nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -22673,9 +22600,9 @@
"integrity": "sha512-paR9M9ZT7rKbh2boksNUynuSZMHhqRYnEZOm/KrZTjQ4/FzyhjLHuvw/8XYzP+E7fS4+/Ms/82EN1pl/OFsiIA=="
},
"@nestjs/axios": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.0.8.tgz",
- "integrity": "sha512-oJyfR9/h9tVk776il0829xyj3b2e81yTu6HjPraxynwNtMNGqZBHHmAQL24yMB3tVbBM0RvG3eUXH8+pRCGwlg==",
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.1.0.tgz",
+ "integrity": "sha512-b2TT2X6BFbnNoeteiaxCIiHaFcSbVW+S5yygYqiIq5i6H77yIU3IVuLdpQkHq8/EqOWFwMopLN8jdkUT71Am9w==",
"dev": true,
"requires": {
"axios": "0.27.2"
@@ -22694,35 +22621,21 @@
}
},
"@nestjs/common": {
- "version": "8.4.7",
- "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-8.4.7.tgz",
- "integrity": "sha512-m/YsbcBal+gA5CFrDpqXqsSfylo+DIQrkFY3qhVIltsYRfu8ct8J9pqsTO6OPf3mvqdOpFGrV5sBjoyAzOBvsw==",
+ "version": "9.3.11",
+ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.3.11.tgz",
+ "integrity": "sha512-IFZ2G/5UKWC2Uo7tJ4SxGed2+aiA+sJyWeWsGTogKVDhq90oxVBToh+uCDeI31HNUpqYGoWmkletfty42zUd8A==",
"dev": true,
- "peer": true,
"requires": {
- "axios": "0.27.2",
"iterare": "1.2.1",
- "tslib": "2.4.0",
- "uuid": "8.3.2"
+ "tslib": "2.5.0",
+ "uid": "2.0.1"
},
"dependencies": {
- "axios": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
- "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
- "dev": true,
- "peer": true,
- "requires": {
- "follow-redirects": "^1.14.9",
- "form-data": "^4.0.0"
- }
- },
"tslib": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
- "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
- "dev": true,
- "peer": true
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
+ "dev": true
}
}
},
@@ -22830,12 +22743,12 @@
}
},
"@openapitools/openapi-generator-cli": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.6.0.tgz",
- "integrity": "sha512-M/aOpR7G+Y1nMf+ofuar8pGszajgfhs1aSPSijkcr2tHTxKAI3sA3YYcOGbszxaNRKFyvOcDq+KP9pcJvKoCHg==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.7.0.tgz",
+ "integrity": "sha512-ieEpHTA/KsDz7ANw03lLPYyjdedDEXYEyYoGBRWdduqXWSX65CJtttjqa8ZaB1mNmIjMtchUHwAYQmTLVQ8HYg==",
"dev": true,
"requires": {
- "@nestjs/axios": "0.0.8",
+ "@nestjs/axios": "0.1.0",
"@nestjs/common": "9.3.11",
"@nestjs/core": "9.3.11",
"@nuxtjs/opencollective": "0.3.2",
@@ -22853,25 +22766,6 @@
"tslib": "2.0.3"
},
"dependencies": {
- "@nestjs/common": {
- "version": "9.3.11",
- "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.3.11.tgz",
- "integrity": "sha512-IFZ2G/5UKWC2Uo7tJ4SxGed2+aiA+sJyWeWsGTogKVDhq90oxVBToh+uCDeI31HNUpqYGoWmkletfty42zUd8A==",
- "dev": true,
- "requires": {
- "iterare": "1.2.1",
- "tslib": "2.5.0",
- "uid": "2.0.1"
- },
- "dependencies": {
- "tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
- "dev": true
- }
- }
- },
"@nestjs/core": {
"version": "9.3.11",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.3.11.tgz",
@@ -23423,9 +23317,9 @@
}
},
"@types/web-bluetooth": {
- "version": "0.0.17",
- "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz",
- "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA=="
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.18.tgz",
+ "integrity": "sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw=="
},
"@types/webpack-env": {
"version": "1.18.0",
@@ -24187,9 +24081,9 @@
}
},
"@vue/vue-loader-v15": {
- "version": "npm:vue-loader@15.10.1",
- "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.1.tgz",
- "integrity": "sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==",
+ "version": "npm:vue-loader@15.10.2",
+ "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.2.tgz",
+ "integrity": "sha512-ndeSe/8KQc/nlA7TJ+OBhv2qalmj1s+uBs7yHDRFaAXscFTApBzY9F1jES3bautmgWjDlDct0fw8rPuySDLwxw==",
"dev": true,
"requires": {
"@vue/component-compiler-utils": "^3.1.0",
@@ -24235,83 +24129,59 @@
"dev": true
},
"@vueuse/components": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.3.0.tgz",
- "integrity": "sha512-EeZz3kjmJI7bH7JSxxMlLyk21LGl6GQjXfpl2n/GiI9QSJi+BVzIra5kEty5eM8McwAanx3e/HnK4drYTgFOWw==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.5.0.tgz",
+ "integrity": "sha512-zWQZ8zkNBvX++VHfyiUaQ4otb+4PWI8679GR8FvdrNnj+01LXnqvrkyKd8yTCMJ9nHqwRRTJikS5fu4Zspn9DQ==",
"requires": {
- "@vueuse/core": "10.3.0",
- "@vueuse/shared": "10.3.0",
- "vue-demi": ">=0.14.5"
+ "@vueuse/core": "10.5.0",
+ "@vueuse/shared": "10.5.0",
+ "vue-demi": ">=0.14.6"
},
"dependencies": {
- "@vueuse/core": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.3.0.tgz",
- "integrity": "sha512-BEM5yxcFKb5btFjTSAFjTu5jmwoW66fyV9uJIP4wUXXU8aR5Hl44gndaaXp7dC5HSObmgbnR2RN+Un1p68Mf5Q==",
- "requires": {
- "@types/web-bluetooth": "^0.0.17",
- "@vueuse/metadata": "10.3.0",
- "@vueuse/shared": "10.3.0",
- "vue-demi": ">=0.14.5"
- }
- },
- "@vueuse/metadata": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.3.0.tgz",
- "integrity": "sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw=="
- },
"vue-demi": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
- "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"requires": {}
}
}
},
"@vueuse/core": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.4.1.tgz",
- "integrity": "sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.5.0.tgz",
+ "integrity": "sha512-z/tI2eSvxwLRjOhDm0h/SXAjNm8N5ld6/SC/JQs6o6kpJ6Ya50LnEL8g5hoYu005i28L0zqB5L5yAl8Jl26K3A==",
"requires": {
- "@types/web-bluetooth": "^0.0.17",
- "@vueuse/metadata": "10.4.1",
- "@vueuse/shared": "10.4.1",
- "vue-demi": ">=0.14.5"
+ "@types/web-bluetooth": "^0.0.18",
+ "@vueuse/metadata": "10.5.0",
+ "@vueuse/shared": "10.5.0",
+ "vue-demi": ">=0.14.6"
},
"dependencies": {
- "@vueuse/shared": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.4.1.tgz",
- "integrity": "sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==",
- "requires": {
- "vue-demi": ">=0.14.5"
- }
- },
"vue-demi": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
- "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"requires": {}
}
}
},
"@vueuse/metadata": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.4.1.tgz",
- "integrity": "sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg=="
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.5.0.tgz",
+ "integrity": "sha512-fEbElR+MaIYyCkeM0SzWkdoMtOpIwO72x8WsZHRE7IggiOlILttqttM69AS13nrDxosnDBYdyy3C5mR1LCxHsw=="
},
"@vueuse/shared": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.3.0.tgz",
- "integrity": "sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.5.0.tgz",
+ "integrity": "sha512-18iyxbbHYLst9MqU1X1QNdMHIjks6wC7XTVf0KNOv5es/Ms6gjVFCAAWTVP2JStuGqydg3DT+ExpFORUEi9yhg==",
"requires": {
- "vue-demi": ">=0.14.5"
+ "vue-demi": ">=0.14.6"
},
"dependencies": {
"vue-demi": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
- "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"requires": {}
}
}
@@ -26088,9 +25958,9 @@
"dev": true
},
"dayjs": {
- "version": "1.11.9",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz",
- "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
+ "version": "1.11.10",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
},
"de-indent": {
"version": "1.0.2",
@@ -31517,9 +31387,9 @@
"dev": true
},
"nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
},
"natural-compare": {
"version": "1.4.0",
@@ -32233,11 +32103,11 @@
}
},
"postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"requires": {
- "nanoid": "^3.3.4",
+ "nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
diff --git a/package.json b/package.json
index 0bd4ad9875..a4d670f4bf 100644
--- a/package.json
+++ b/package.json
@@ -21,10 +21,10 @@
"@lumieducation/h5p-webcomponents": "^9.2.2",
"@mdi/js": "^7.2.96",
"@types/vuelidate": "^0.7.18",
- "@vueuse/components": "^10.3.0",
+ "@vueuse/components": "^10.5.0",
"@vueuse/core": "^10.4.1",
"axios": "^1.5.0",
- "dayjs": "^1.11.9",
+ "dayjs": "^1.11.10",
"flush-promises": "^1.0.2",
"katex": "^0.12.0",
"kjua": "^0.9.0",
@@ -48,7 +48,7 @@
"devDependencies": {
"@golevelup/ts-jest": "^0.4.0",
"@intlify/vue-i18n-loader": "^4.0.0",
- "@openapitools/openapi-generator-cli": "^2.5.2",
+ "@openapitools/openapi-generator-cli": "^2.7.0",
"@types/jest": "^27.5.2",
"@types/object-hash": "^2.2.1",
"@typescript-eslint/eslint-plugin": "^5.4.0",
diff --git a/src/components/administration/AdminMigrationSection.unit.ts b/src/components/administration/AdminMigrationSection.unit.ts
index be068c5113..74fbdfc46a 100644
--- a/src/components/administration/AdminMigrationSection.unit.ts
+++ b/src/components/administration/AdminMigrationSection.unit.ts
@@ -567,7 +567,7 @@ describe("AdminMigrationSection", () => {
userLoginMigrationModule.startUserLoginMigration
).toHaveBeenCalled();
expect(
- userLoginMigrationModule.fetchLatestUserLoginMigrationForCurrentUser
+ userLoginMigrationModule.fetchLatestUserLoginMigrationForSchool
).toHaveBeenCalled();
});
});
@@ -676,7 +676,7 @@ describe("AdminMigrationSection", () => {
userLoginMigrationModule.closeUserLoginMigration
).toHaveBeenCalled();
expect(
- userLoginMigrationModule.fetchLatestUserLoginMigrationForCurrentUser
+ userLoginMigrationModule.fetchLatestUserLoginMigrationForSchool
).toHaveBeenCalled();
});
});
diff --git a/src/components/administration/AdminMigrationSection.vue b/src/components/administration/AdminMigrationSection.vue
index a4b86781ac..737c31f348 100644
--- a/src/components/administration/AdminMigrationSection.vue
+++ b/src/components/administration/AdminMigrationSection.vue
@@ -201,7 +201,7 @@ export default defineComponent({
onMounted(async () => {
// TODO remove in https://ticketsystem.dbildungscloud.de/browse/N21-820
await schoolsModule.fetchSchoolOAuthMigration();
- await userLoginMigrationModule.fetchLatestUserLoginMigrationForCurrentUser();
+ await userLoginMigrationModule.fetchLatestUserLoginMigrationForSchool();
});
const userLoginMigration: ComputedRef =
diff --git a/src/components/feature-board-file-element/FileContentElement.unit.ts b/src/components/feature-board-file-element/FileContentElement.unit.ts
index 50d7b4e953..10165a32ab 100644
--- a/src/components/feature-board-file-element/FileContentElement.unit.ts
+++ b/src/components/feature-board-file-element/FileContentElement.unit.ts
@@ -13,10 +13,12 @@ import createComponentMocks from "@@/tests/test-utils/componentMocks";
import { fileElementResponseFactory } from "@@/tests/test-utils/factory/fileElementResponseFactory";
import { fileRecordResponseFactory } from "@@/tests/test-utils/factory/filerecordResponse.factory";
import { MountOptions, shallowMount } from "@vue/test-utils";
-import Vue from "vue";
-import FileContentElement from "./FileContentElement.vue";
+import Vue, { computed } from "vue";
+import { useFileAlerts } from "./content/alert/useFileAlerts.composable";
import FileContent from "./content/FileContent.vue";
+import FileContentElement from "./FileContentElement.vue";
import { FileProperties } from "./shared/types/file-properties";
+import { FileAlert } from "./shared/types/FileAlert.enum";
import FileUpload from "./upload/FileUpload.vue";
jest.mock("@data-board", () => {
@@ -27,6 +29,7 @@ jest.mock("@data-board", () => {
});
jest.mock("@feature-board");
jest.mock("./shared/composables/FileStorageApi.composable");
+jest.mock("./content/alert/useFileAlerts.composable");
describe("FileContentElement", () => {
const notifierModule = createModuleMocks(NotifierModule);
@@ -35,6 +38,13 @@ describe("FileContentElement", () => {
isEditMode: boolean;
}) => {
const menu = "slot-menu";
+
+ const addAlertMock = jest.fn();
+ jest.mocked(useFileAlerts).mockReturnValue({
+ addAlert: addAlertMock,
+ alerts: computed(() => []),
+ });
+
const wrapper = shallowMount(FileContentElement as MountOptions, {
...createComponentMocks({ i18n: true }),
provide: {
@@ -47,7 +57,7 @@ describe("FileContentElement", () => {
},
});
- return { wrapper, menu };
+ return { wrapper, menu, addAlertMock };
};
describe("when component is not in edit mode", () => {
@@ -142,9 +152,10 @@ describe("FileContentElement", () => {
size: fileRecordResponse.size,
previewStatus: fileRecordResponse.previewStatus,
element,
+ mimeType: fileRecordResponse.mimeType,
};
- const { wrapper, menu } = getWrapper({
+ const { wrapper, menu, addAlertMock } = getWrapper({
element,
isEditMode: false,
});
@@ -156,6 +167,7 @@ describe("FileContentElement", () => {
element,
expectedFileProperties,
menu,
+ addAlertMock,
};
};
@@ -167,6 +179,35 @@ describe("FileContentElement", () => {
expect(card.props("outlined")).toBe(false);
});
+ describe("when file content emits add:alert event", () => {
+ it("should add event payload to emittedAlerts", async () => {
+ const { wrapper, addAlertMock } = setup();
+
+ await wrapper.vm.$nextTick();
+
+ const fileContent = wrapper.findComponent(FileContent);
+ const alert = FileAlert.VIDEO_FORMAT_ERROR;
+ fileContent.vm.$emit("add:alert", alert);
+
+ expect(addAlertMock).toHaveBeenCalledWith(alert);
+ });
+ });
+
+ describe("when file content emits fetch:file event", () => {
+ it("should call fetchFile when FileContent emits fetch:file event", async () => {
+ const { wrapper, fetchFile } = setup();
+
+ await wrapper.vm.$nextTick();
+
+ expect(fetchFile).toHaveBeenCalledTimes(1);
+
+ const fileContent = wrapper.findComponent(FileContent);
+ fileContent.vm.$emit("fetch:file");
+
+ expect(fetchFile).toHaveBeenCalledTimes(2);
+ });
+ });
+
describe("when v-card emits keydown.down event", () => {
it("should emit move-keyboard:edit event", async () => {
const { wrapper } = setup();
@@ -244,21 +285,6 @@ describe("FileContentElement", () => {
expect(fileProperties).toEqual(expectedFileProperties);
});
- it("should call fetchFile when FileContent emits fetch:file event", async () => {
- const { wrapper, fetchFile } = setup();
-
- await wrapper.vm.$nextTick();
-
- expect(fetchFile).toHaveBeenCalledTimes(1);
-
- const fileContent = wrapper.findComponent(FileContent);
- fileContent.vm.$emit("fetch:file");
-
- await wrapper.vm.$nextTick();
-
- expect(fetchFile).toHaveBeenCalledTimes(2);
- });
-
it("should not render File Upload component", async () => {
const { wrapper } = setup();
await wrapper.vm.$nextTick();
@@ -455,6 +481,7 @@ describe("FileContentElement", () => {
size: fileRecordResponse.size,
previewStatus: fileRecordResponse.previewStatus,
element,
+ mimeType: fileRecordResponse.mimeType,
};
const { wrapper, menu } = getWrapper({
diff --git a/src/components/feature-board-file-element/FileContentElement.vue b/src/components/feature-board-file-element/FileContentElement.vue
index bc76bf5b16..7fc570029d 100644
--- a/src/components/feature-board-file-element/FileContentElement.vue
+++ b/src/components/feature-board-file-element/FileContentElement.vue
@@ -13,10 +13,12 @@
@@ -61,8 +63,10 @@ import {
ref,
toRef,
} from "vue";
+import { useFileAlerts } from "./content/alert/useFileAlerts.composable";
import FileContent from "./content/FileContent.vue";
import { useFileStorageApi } from "./shared/composables/FileStorageApi.composable";
+import { FileAlert } from "./shared/types/FileAlert.enum";
import FileUpload from "./upload/FileUpload.vue";
export default defineComponent({
@@ -97,6 +101,8 @@ export default defineComponent({
FileRecordParentType.BOARDNODES
);
+ const { alerts, addAlert } = useFileAlerts(fileRecord);
+
const fileProperties = computed(() => {
if (fileRecord.value === undefined) {
return;
@@ -115,6 +121,7 @@ export default defineComponent({
isDownloadAllowed: isDownloadAllowed(
fileRecord.value.securityCheckStatus
),
+ mimeType: fileRecord.value.mimeType,
element: props.element,
};
});
@@ -161,6 +168,9 @@ export default defineComponent({
modelValue.value.caption = value;
};
+ const onAddAlert = (alert: FileAlert) => {
+ addAlert(alert);
+ };
const onDelete = () => emit("delete:element", element.value.id);
const onMoveUp = () => emit("move-up:edit");
const onMoveDown = () => emit("move-down:edit");
@@ -172,11 +182,13 @@ export default defineComponent({
hasFileRecord,
isOutlined,
modelValue,
+ alerts,
onKeydownArrow,
onUploadFile,
onFetchFile,
onUpdateAlternativeText,
onUpdateCaption,
+ onAddAlert,
onDelete,
onMoveUp,
onMoveDown,
diff --git a/src/components/feature-board-file-element/content/FileContent.unit.ts b/src/components/feature-board-file-element/content/FileContent.unit.ts
index 39d94f0f24..37332edd23 100644
--- a/src/components/feature-board-file-element/content/FileContent.unit.ts
+++ b/src/components/feature-board-file-element/content/FileContent.unit.ts
@@ -2,8 +2,10 @@ import { PreviewStatus } from "@/fileStorageApi/v3";
import { fileElementResponseFactory } from "@@/tests/test-utils";
import createComponentMocks from "@@/tests/test-utils/componentMocks";
import { shallowMount } from "@vue/test-utils";
+import { FileAlert } from "../shared/types/FileAlert.enum";
+import FileAlerts from "./alert/FileAlerts.vue";
+import FileDisplay from "./display/FileDisplay.vue";
import FileContent from "./FileContent.vue";
-import FileAlert from "./alert/FileAlert.vue";
import ContentElementFooter from "./footer/ContentElementFooter.vue";
import FileInputs from "./inputs/FileInputs.vue";
@@ -24,10 +26,13 @@ describe("FileContent", () => {
isDownloadAllowed: true,
element,
};
+
+ const alerts = [FileAlert.AWAITING_SCAN_STATUS];
const wrapper = shallowMount(FileContent, {
propsData: {
fileProperties,
isEditMode: true,
+ alerts,
},
...createComponentMocks({}),
});
@@ -35,13 +40,14 @@ describe("FileContent", () => {
return {
wrapper,
fileProperties,
+ alerts,
};
};
- it("should pass props to FileContent", () => {
+ it("should pass props to FileDisplay", () => {
const { wrapper, fileProperties } = setup();
- const fileContent = wrapper.findComponent(FileContent);
+ const fileContent = wrapper.findComponent(FileDisplay);
expect(fileContent.props()).toEqual({
fileProperties,
@@ -61,54 +67,73 @@ describe("FileContent", () => {
});
it("should pass props to FileAlert", () => {
- const { wrapper, fileProperties } = setup();
+ const { wrapper, alerts } = setup();
- const fileAlert = wrapper.findComponent(FileAlert);
+ const fileAlert = wrapper.findComponent(FileAlerts);
expect(fileAlert.props()).toEqual({
- previewStatus: fileProperties.previewStatus,
+ alerts,
});
});
- it("should FileInputs component be in dom", () => {
- const { wrapper } = setup();
+ it("should pass props to FileInputs", () => {
+ const { wrapper, fileProperties } = setup();
const fileInputs = wrapper.findComponent(FileInputs);
- expect(fileInputs.exists()).toBe(true);
+ expect(fileInputs.props()).toEqual({
+ fileProperties,
+ isEditMode: true,
+ });
});
- it("should emit update:alternativeText event, when it receives update:text event from file inputs component", async () => {
- const { wrapper } = setup();
+ describe("when file inputs emit update:alternativeText", () => {
+ it("should emit update:alternativeText", async () => {
+ const { wrapper } = setup();
- const fileInputs = wrapper.findComponent(FileInputs);
+ const fileInputs = wrapper.findComponent(FileInputs);
- fileInputs.vm.$emit("update:alternativeText");
+ fileInputs.vm.$emit("update:alternativeText");
- expect(wrapper.emitted("update:alternativeText")).toHaveLength(1);
+ expect(wrapper.emitted("update:alternativeText")).toHaveLength(1);
+ });
});
- it("should emit update:caption event, when it receives update:caption event from file inputs component", async () => {
- const { wrapper } = setup();
+ describe("when file inputs emit update:caption", () => {
+ it("should emit update:caption event, when it receives update:caption event from file inputs component", async () => {
+ const { wrapper } = setup();
- const fileInputs = wrapper.findComponent(FileInputs);
+ const fileInputs = wrapper.findComponent(FileInputs);
- fileInputs.vm.$emit("update:caption");
+ fileInputs.vm.$emit("update:caption");
- expect(wrapper.emitted("update:caption")).toHaveLength(1);
+ expect(wrapper.emitted("update:caption")).toHaveLength(1);
+ });
});
- describe("when alert emits on-status-reload", () => {
+ describe("when file alerts emits on-status-reload", () => {
it("should emit fetch:file event", async () => {
const { wrapper } = setup();
- const fileAlert = wrapper.findComponent(FileAlert);
+ const fileAlert = wrapper.findComponent(FileAlerts);
await fileAlert.vm.$emit("on-status-reload");
expect(wrapper.emitted("fetch:file")).toBeTruthy();
});
});
+
+ describe("when file display emits add:alert", () => {
+ it("should emit add:alert event", async () => {
+ const { wrapper } = setup();
+
+ const fileDisplay = wrapper.findComponent(FileDisplay);
+
+ fileDisplay.vm.$emit("add:alert");
+
+ expect(wrapper.emitted("add:alert")).toHaveLength(1);
+ });
+ });
});
});
});
diff --git a/src/components/feature-board-file-element/content/FileContent.vue b/src/components/feature-board-file-element/content/FileContent.vue
index c0c22e623b..34e546110b 100644
--- a/src/components/feature-board-file-element/content/FileContent.vue
+++ b/src/components/feature-board-file-element/content/FileContent.vue
@@ -1,6 +1,10 @@
-
+
-
+
diff --git a/src/components/feature-board-file-element/content/alert/FileAlerts.unit.ts b/src/components/feature-board-file-element/content/alert/FileAlerts.unit.ts
new file mode 100644
index 0000000000..abcd4d9161
--- /dev/null
+++ b/src/components/feature-board-file-element/content/alert/FileAlerts.unit.ts
@@ -0,0 +1,104 @@
+import { I18N_KEY } from "@/utils/inject";
+import { i18nMock } from "@@/tests/test-utils";
+import createComponentMocks from "@@/tests/test-utils/componentMocks";
+import { ErrorAlert, InfoAlert, WarningAlert } from "@ui-alert";
+import { shallowMount } from "@vue/test-utils";
+import { FileAlert } from "../../shared/types/FileAlert.enum";
+import FileAlerts from "./FileAlerts.vue";
+
+describe("FileAlerts", () => {
+ const setup = (alerts: FileAlert[]) => {
+ document.body.setAttribute("data-app", "true");
+
+ const wrapper = shallowMount(FileAlerts, {
+ ...createComponentMocks({ i18n: true }),
+ provide: {
+ [I18N_KEY.valueOf()]: i18nMock,
+ },
+ propsData: { alerts },
+ });
+ return { wrapper, alerts };
+ };
+
+ describe("when alerts contains FileAlert.VIDEO_FORMAT_ERROR", () => {
+ it("should render FileAlert.VIDEO_FORMAT_ERROR", () => {
+ const { wrapper } = setup([FileAlert.VIDEO_FORMAT_ERROR]);
+
+ const infoAlert = wrapper.findComponent(InfoAlert);
+
+ expect(infoAlert.text()).toBe(
+ "components.cardElement.fileElement.videoFormatError"
+ );
+ });
+ });
+
+ describe("when alerts contains FileAlert.AWAITING_SCAN_STATUS", () => {
+ it("should render FileAlert.AWAITING_SCAN_STATUS", () => {
+ const { wrapper } = setup([FileAlert.AWAITING_SCAN_STATUS]);
+
+ const infoAlert = wrapper.findComponent(InfoAlert);
+
+ expect(infoAlert.text()).toContain(
+ "components.cardElement.fileElement.awaitingScan"
+ );
+ expect(infoAlert.text()).toContain(
+ "components.cardElement.fileElement.reloadStatus"
+ );
+ });
+ });
+
+ describe("when alerts contains FileAlert.SCAN_STATUS_WONT_CHECK", () => {
+ it("should render FileAlert.SCAN_STATUS_WONT_CHECK", () => {
+ const { wrapper } = setup([FileAlert.SCAN_STATUS_WONT_CHECK]);
+
+ const infoAlert = wrapper.findComponent(InfoAlert);
+
+ expect(infoAlert.text()).toBe(
+ "components.cardElement.fileElement.scanWontCheck"
+ );
+ });
+ });
+
+ describe("when alerts contains FileAlert.SCAN_STATUS_ERROR", () => {
+ it("should render FileAlert.SCAN_STATUS_ERROR", () => {
+ const { wrapper } = setup([FileAlert.SCAN_STATUS_ERROR]);
+
+ const warningAlert = wrapper.findComponent(WarningAlert);
+
+ expect(warningAlert.text()).toBe(
+ "components.cardElement.fileElement.scanError"
+ );
+ });
+ });
+
+ describe("when alerts contains FileAlert.SCAN_STATUS_BLOCKED", () => {
+ it("should render FileAlert.SCAN_STATUS_BLOCKED", () => {
+ const { wrapper } = setup([FileAlert.SCAN_STATUS_BLOCKED]);
+
+ const errorAlert = wrapper.findComponent(ErrorAlert);
+
+ expect(errorAlert.text()).toBe(
+ "components.cardElement.fileElement.virusDetected"
+ );
+ });
+ });
+
+ describe("when alerts contains SCAN_STATUS_BLOCKED and VIDEO_FORMAT_ERROR", () => {
+ it("should render FileAlert.SCAN_STATUS_BLOCKED", () => {
+ const { wrapper } = setup([
+ FileAlert.SCAN_STATUS_BLOCKED,
+ FileAlert.VIDEO_FORMAT_ERROR,
+ ]);
+
+ const errorAlert = wrapper.findComponent(ErrorAlert);
+ expect(errorAlert.text()).toContain(
+ "components.cardElement.fileElement.virusDetected"
+ );
+
+ const infoAlert = wrapper.findComponent(InfoAlert);
+ expect(infoAlert.text()).toBe(
+ "components.cardElement.fileElement.videoFormatError"
+ );
+ });
+ });
+});
diff --git a/src/components/feature-board-file-element/content/alert/FileAlerts.vue b/src/components/feature-board-file-element/content/alert/FileAlerts.vue
new file mode 100644
index 0000000000..bc14cbd193
--- /dev/null
+++ b/src/components/feature-board-file-element/content/alert/FileAlerts.vue
@@ -0,0 +1,56 @@
+
+
+
+ {{ t("components.cardElement.fileElement.videoFormatError") }}
+
+
+
+
+
+
+
+ {{ t("components.cardElement.fileElement.scanWontCheck") }}
+
+
+
+ {{ t("components.cardElement.fileElement.virusDetected") }}
+
+
+
+ {{ t("components.cardElement.fileElement.scanError") }}
+
+
+
+
+
diff --git a/src/components/feature-board-file-element/content/alert/useFileAlerts.composable.ts b/src/components/feature-board-file-element/content/alert/useFileAlerts.composable.ts
new file mode 100644
index 0000000000..361eb94df5
--- /dev/null
+++ b/src/components/feature-board-file-element/content/alert/useFileAlerts.composable.ts
@@ -0,0 +1,47 @@
+import { FileRecordResponse, PreviewStatus } from "@/fileStorageApi/v3";
+import { computed, Ref, ref } from "vue";
+import { FileAlert } from "../../shared/types/FileAlert.enum";
+
+export const useFileAlerts = (
+ fileRecord: Ref
+) => {
+ const emittedAlerts: Ref = ref([]);
+
+ const previewStatusAlert = computed(() => {
+ return mapPreviewStatusToFileAlert(fileRecord?.value?.previewStatus);
+ });
+
+ const alerts = computed(() => {
+ const alerts = [...emittedAlerts.value];
+
+ if (previewStatusAlert.value !== undefined)
+ alerts.push(previewStatusAlert.value);
+
+ return alerts;
+ });
+
+ const addAlert = (alert: FileAlert) => {
+ emittedAlerts.value.push(alert);
+ };
+
+ return {
+ alerts,
+ addAlert,
+ };
+};
+
+function mapPreviewStatusToFileAlert(
+ previewStatus?: PreviewStatus
+): FileAlert | undefined {
+ console.log(previewStatus);
+ if (previewStatus === PreviewStatus.AWAITING_SCAN_STATUS)
+ return FileAlert.AWAITING_SCAN_STATUS;
+ if (previewStatus === PreviewStatus.PREVIEW_NOT_POSSIBLE_SCAN_STATUS_BLOCKED)
+ return FileAlert.SCAN_STATUS_BLOCKED;
+ if (previewStatus === PreviewStatus.PREVIEW_NOT_POSSIBLE_SCAN_STATUS_ERROR)
+ return FileAlert.SCAN_STATUS_ERROR;
+ if (
+ previewStatus === PreviewStatus.PREVIEW_NOT_POSSIBLE_SCAN_STATUS_WONT_CHECK
+ )
+ return FileAlert.SCAN_STATUS_WONT_CHECK;
+}
diff --git a/src/components/feature-board-file-element/content/alert/useFileAlerts.composable.unit.ts b/src/components/feature-board-file-element/content/alert/useFileAlerts.composable.unit.ts
new file mode 100644
index 0000000000..d0cb3fb0ec
--- /dev/null
+++ b/src/components/feature-board-file-element/content/alert/useFileAlerts.composable.unit.ts
@@ -0,0 +1,105 @@
+import { PreviewStatus } from "@/fileStorageApi/v3";
+import {
+ fileRecordResponseFactory,
+ mountComposable,
+} from "@@/tests/test-utils";
+import { ref } from "vue";
+import { FileAlert } from "../../shared/types/FileAlert.enum";
+import { useFileAlerts } from "./useFileAlerts.composable";
+
+describe("useFileAlerts", () => {
+ describe("when filerecord is undefined", () => {
+ const setup = () => {
+ const fileRecord = ref();
+ const { alerts } = mountComposable(() => useFileAlerts(fileRecord));
+
+ return {
+ alerts,
+ };
+ };
+
+ it("should return alerts empty", () => {
+ const { alerts } = setup();
+ expect(alerts.value).toEqual([]);
+ });
+ });
+
+ describe("when filerecord is defined", () => {
+ const setup = (previewStatus: PreviewStatus) => {
+ const fileRecord = ref(
+ fileRecordResponseFactory.build({ previewStatus })
+ );
+ const { alerts, addAlert } = mountComposable(() =>
+ useFileAlerts(fileRecord)
+ );
+
+ return {
+ alerts,
+ fileRecord,
+ addAlert,
+ };
+ };
+
+ describe("when previewStatus is AWAITING_SCAN_STATUS", () => {
+ it("should return AWAITING_SCAN_STATUS alert", () => {
+ const { alerts } = setup(PreviewStatus.AWAITING_SCAN_STATUS);
+ expect(alerts.value).toEqual([FileAlert.AWAITING_SCAN_STATUS]);
+ });
+ });
+
+ describe("when previewStatus is PREVIEW_NOT_POSSIBLE_SCAN_STATUS_BLOCKED", () => {
+ it("should return SCAN_STATUS_BLOCKED alert", () => {
+ const { alerts } = setup(
+ PreviewStatus.PREVIEW_NOT_POSSIBLE_SCAN_STATUS_BLOCKED
+ );
+ expect(alerts.value).toEqual([FileAlert.SCAN_STATUS_BLOCKED]);
+ });
+ });
+
+ describe("when previewStatus is PREVIEW_NOT_POSSIBLE_SCAN_STATUS_ERROR", () => {
+ it("should return SCAN_STATUS_ERROR alert", () => {
+ const { alerts } = setup(
+ PreviewStatus.PREVIEW_NOT_POSSIBLE_SCAN_STATUS_ERROR
+ );
+ expect(alerts.value).toEqual([FileAlert.SCAN_STATUS_ERROR]);
+ });
+ });
+
+ describe("when previewStatus is PREVIEW_NOT_POSSIBLE_SCAN_STATUS_WONT_CHECK", () => {
+ it("should return SCAN_STATUS_WONT_CHECK alert", () => {
+ const { alerts } = setup(
+ PreviewStatus.PREVIEW_NOT_POSSIBLE_SCAN_STATUS_WONT_CHECK
+ );
+
+ expect(alerts.value).toEqual([FileAlert.SCAN_STATUS_WONT_CHECK]);
+ });
+ });
+
+ describe("when previewStatus changes from AWAITING_SCAN_STATUS to PREVIEW_POSSIBLE", () => {
+ it("should return SCAN_STATUS_WONT_CHECK alert", () => {
+ const { alerts, fileRecord } = setup(
+ PreviewStatus.AWAITING_SCAN_STATUS
+ );
+
+ fileRecord.value = fileRecordResponseFactory.build({
+ previewStatus: PreviewStatus.PREVIEW_POSSIBLE,
+ });
+
+ expect(alerts.value).toEqual([]);
+ });
+ });
+
+ describe("when alert is added", () => {
+ it("should return correct alerts array", () => {
+ const { alerts, addAlert } = setup(PreviewStatus.AWAITING_SCAN_STATUS);
+
+ addAlert(FileAlert.VIDEO_FORMAT_ERROR);
+
+ expect(alerts.value).toEqual([
+ FileAlert.VIDEO_FORMAT_ERROR,
+ FileAlert.AWAITING_SCAN_STATUS,
+ ]);
+ });
+ });
+ });
+});
diff --git a/src/components/feature-board-file-element/content/display/FileDisplay.unit.ts b/src/components/feature-board-file-element/content/display/FileDisplay.unit.ts
index 3cad2add66..385beeafbd 100644
--- a/src/components/feature-board-file-element/content/display/FileDisplay.unit.ts
+++ b/src/components/feature-board-file-element/content/display/FileDisplay.unit.ts
@@ -1,112 +1,244 @@
+import { isVideoMimeType } from "@/utils/fileHelper";
import { fileElementResponseFactory } from "@@/tests/test-utils";
import createComponentMocks from "@@/tests/test-utils/componentMocks";
import { shallowMount } from "@vue/test-utils";
-import FileDisplay from "./FileDisplay.vue";
import FileDescription from "./file-description/FileDescription.vue";
+import FileDisplay from "./FileDisplay.vue";
import ImageDisplay from "./image-display/ImageDisplay.vue";
+import VideoDisplay from "./video-display/VideoDisplay.vue";
+
+jest.mock("@/utils/fileHelper");
+const isVideoMimeTypeMock = jest.mocked(isVideoMimeType);
describe("FileDisplay", () => {
describe("when previewUrl is defined", () => {
- const setup = () => {
- document.body.setAttribute("data-app", "true");
-
- const element = fileElementResponseFactory.build();
- const propsData = {
- fileProperties: {
- name: "test",
- size: 100,
- url: "test",
- previewUrl: "test",
- previewStatus: "test",
- isDownloadAllowed: true,
- element,
- },
- isEditMode: true,
+ describe("when mimeType is not a video type", () => {
+ const setup = () => {
+ document.body.setAttribute("data-app", "true");
+
+ const element = fileElementResponseFactory.build();
+ const propsData = {
+ fileProperties: {
+ name: "test",
+ size: 100,
+ url: "test",
+ previewUrl: "test",
+ previewStatus: "test",
+ isDownloadAllowed: true,
+ element,
+ },
+ isEditMode: true,
+ };
+
+ isVideoMimeTypeMock.mockReset();
+ isVideoMimeTypeMock.mockReturnValueOnce(false);
+
+ const wrapper = shallowMount(FileDisplay, {
+ propsData,
+ ...createComponentMocks({}),
+ });
+
+ return {
+ wrapper,
+ fileNameProp: propsData.fileProperties.name,
+ previewUrlProp: propsData.fileProperties.previewUrl,
+ srcProp: propsData.fileProperties.url,
+ };
};
- const wrapper = shallowMount(FileDisplay, {
- propsData,
- ...createComponentMocks({}),
+ it("should be found in dom", () => {
+ const { wrapper } = setup();
+
+ const fileDisplay = wrapper.findComponent(FileDisplay);
+
+ expect(fileDisplay.exists()).toBe(true);
});
- return {
- wrapper,
- fileNameProp: propsData.fileProperties.name,
- previewUrlProp: propsData.fileProperties.previewUrl,
- };
- };
+ it("should pass correct props to image display component", async () => {
+ const { wrapper, fileNameProp, previewUrlProp, srcProp } = setup();
+
+ const props = wrapper.findComponent(ImageDisplay).attributes();
+ expect(props.name).toBe(fileNameProp);
+ expect(props.previewsrc).toBe(previewUrlProp);
+ expect(props.src).toBe(srcProp);
+ expect(props.iseditmode).toBe("true");
+ expect(props.element).toBeDefined();
+ });
- it("should be found in dom", () => {
- const { wrapper } = setup();
+ it("should render file description display component", () => {
+ const { wrapper } = setup();
- const fileDisplay = wrapper.findComponent(FileDisplay);
+ const fileDescription = wrapper.findComponent(FileDescription);
- expect(fileDisplay.exists()).toBe(true);
+ expect(fileDescription.exists()).toBe(true);
+ });
});
- it("should pass correct props to image display component", () => {
- const { wrapper, fileNameProp, previewUrlProp } = setup();
+ describe("when mimeType is a video type", () => {
+ const setup = () => {
+ document.body.setAttribute("data-app", "true");
+
+ const element = fileElementResponseFactory.build();
+ const propsData = {
+ fileProperties: {
+ name: "test",
+ size: 100,
+ url: "test",
+ previewUrl: "test",
+ previewStatus: "test",
+ isDownloadAllowed: true,
+ element,
+ },
+ isEditMode: true,
+ };
+
+ isVideoMimeTypeMock.mockReset();
+ isVideoMimeTypeMock.mockReturnValueOnce(true);
+
+ const wrapper = shallowMount(FileDisplay, {
+ propsData,
+ ...createComponentMocks({}),
+ });
+
+ return {
+ wrapper,
+ fileNameProp: propsData.fileProperties.name,
+ previewUrlProp: propsData.fileProperties.previewUrl,
+ srcProp: propsData.fileProperties.url,
+ };
+ };
- const props = wrapper.findComponent(ImageDisplay).attributes();
+ it("should be found in dom", () => {
+ const { wrapper } = setup();
- expect(props.name).toBe(fileNameProp);
- expect(props.previewurl).toBe(previewUrlProp);
- expect(props.iseditmode).toBe("true");
- });
+ const fileDisplay = wrapper.findComponent(FileDisplay);
+
+ expect(fileDisplay.exists()).toBe(true);
+ });
+
+ it("should pass correct props to image display component", () => {
+ const { wrapper, fileNameProp, previewUrlProp, srcProp } = setup();
- it("should render file description display component", () => {
- const { wrapper } = setup();
+ const props = wrapper.findComponent(ImageDisplay).attributes();
- const fileDescription = wrapper.findComponent(FileDescription);
+ expect(props.name).toBe(fileNameProp);
+ expect(props.previewsrc).toBe(previewUrlProp);
+ expect(props.src).toBe(srcProp);
+ expect(props.iseditmode).toBe("true");
+ expect(props.element).toBeDefined();
+ });
+
+ it("should render file description display component", () => {
+ const { wrapper } = setup();
- expect(fileDescription.exists()).toBe(true);
+ const fileDescription = wrapper.findComponent(FileDescription);
+
+ expect(fileDescription.exists()).toBe(true);
+ });
});
});
describe("when previewUrl is undefined", () => {
- const setup = () => {
- document.body.setAttribute("data-app", "true");
-
- const element = fileElementResponseFactory.build();
- const propsData = {
- fileProperties: {
- name: "test",
- size: 100,
- url: "test",
- previewUrl: undefined,
- previewStatus: "test",
- isDownloadAllowed: true,
- element,
- },
- isEditMode: true,
+ describe("when mimeType is a video type", () => {
+ const setup = () => {
+ document.body.setAttribute("data-app", "true");
+
+ const element = fileElementResponseFactory.build();
+ const propsData = {
+ fileProperties: {
+ name: "test",
+ size: 100,
+ url: "test",
+ previewUrl: undefined,
+ previewStatus: "test",
+ isDownloadAllowed: true,
+ element,
+ },
+ isEditMode: true,
+ };
+
+ isVideoMimeTypeMock.mockReset();
+ isVideoMimeTypeMock.mockReturnValueOnce(true);
+
+ const wrapper = shallowMount(FileDisplay, {
+ propsData,
+ ...createComponentMocks({}),
+ });
+
+ return {
+ wrapper,
+ fileNameProp: propsData.fileProperties.name,
+ previewUrlProp: propsData.fileProperties.previewUrl,
+ url: propsData.fileProperties.url,
+ };
};
- const wrapper = shallowMount(FileDisplay, {
- propsData,
- ...createComponentMocks({}),
+ it("should render video display component", async () => {
+ const { wrapper } = setup();
+
+ const videoDisplay = wrapper.findComponent(VideoDisplay);
+
+ expect(videoDisplay.exists()).toBe(true);
+ });
+
+ it("should pass correct props to video display component", () => {
+ const { wrapper, fileNameProp, url } = setup();
+
+ const props = wrapper.findComponent(VideoDisplay).attributes();
+
+ expect(props.src).toBe(url);
+ expect(props.name).toBe(fileNameProp);
});
+ });
- return {
- wrapper,
- fileNameProp: propsData.fileProperties.name,
- previewUrlProp: propsData.fileProperties.previewUrl,
+ describe("when mimeType is not a video type", () => {
+ const setup = () => {
+ document.body.setAttribute("data-app", "true");
+
+ const element = fileElementResponseFactory.build();
+ const propsData = {
+ fileProperties: {
+ name: "test",
+ size: 100,
+ url: "test",
+ previewUrl: undefined,
+ previewStatus: "test",
+ isDownloadAllowed: true,
+ element,
+ },
+ isEditMode: true,
+ };
+
+ isVideoMimeTypeMock.mockReset();
+ isVideoMimeTypeMock.mockReturnValueOnce(false);
+
+ const wrapper = shallowMount(FileDisplay, {
+ propsData,
+ ...createComponentMocks({}),
+ });
+
+ return {
+ wrapper,
+ fileNameProp: propsData.fileProperties.name,
+ previewUrlProp: propsData.fileProperties.previewUrl,
+ };
};
- };
- it("should render file description display component", () => {
- const { wrapper } = setup();
+ it("should render file description display component", () => {
+ const { wrapper } = setup();
- const fileDescription = wrapper.findComponent(FileDescription);
+ const fileDescription = wrapper.findComponent(FileDescription);
- expect(fileDescription.exists()).toBe(true);
- });
+ expect(fileDescription.exists()).toBe(true);
+ });
- it("should not render image display component", () => {
- const { wrapper } = setup();
+ it("should not render image display component", () => {
+ const { wrapper } = setup();
- const imageDisplay = wrapper.findComponent(ImageDisplay);
+ const imageDisplay = wrapper.findComponent(ImageDisplay);
- expect(imageDisplay.exists()).toBe(false);
+ expect(imageDisplay.exists()).toBe(false);
+ });
});
});
});
diff --git a/src/components/feature-board-file-element/content/display/FileDisplay.vue b/src/components/feature-board-file-element/content/display/FileDisplay.vue
index 6fbc88eef1..f48061188c 100644
--- a/src/components/feature-board-file-element/content/display/FileDisplay.vue
+++ b/src/components/feature-board-file-element/content/display/FileDisplay.vue
@@ -2,18 +2,26 @@
+
+
+
@@ -22,14 +30,17 @@
diff --git a/src/components/feature-board-file-element/content/display/image-display/ImageDisplay.unit.ts b/src/components/feature-board-file-element/content/display/image-display/ImageDisplay.unit.ts
index cd1da23632..0fd54c92f0 100644
--- a/src/components/feature-board-file-element/content/display/image-display/ImageDisplay.unit.ts
+++ b/src/components/feature-board-file-element/content/display/image-display/ImageDisplay.unit.ts
@@ -23,8 +23,8 @@ describe("ImageDisplay", () => {
content: { alternativeText: props.alternativeText },
});
const propsData = {
- url: "url/1/file-record #1.txt",
- previewUrl: "preview/1/file-record #1.txt",
+ src: "url/1/file-record #1.txt",
+ previewSrc: "preview/1/file-record #1.txt",
name: "file-record #1.txt",
isEditMode: props.isEditMode,
element,
@@ -49,9 +49,9 @@ describe("ImageDisplay", () => {
return {
wrapper,
- urlProp: propsData.url,
+ src: propsData.src,
nameProp: propsData.name,
- previewUrlProp: propsData.previewUrl,
+ previewSrc: propsData.previewSrc,
element,
open,
};
@@ -95,11 +95,11 @@ describe("ImageDisplay", () => {
});
it("should have set src correctly", () => {
- const { wrapper, previewUrlProp } = setup({ isEditMode: false });
+ const { wrapper, previewSrc } = setup({ isEditMode: false });
const src = wrapper.find(imageSelektor).attributes("src");
- expect(src).toBe(previewUrlProp);
+ expect(src).toBe(previewSrc);
});
it("should have set alt correctly", () => {
@@ -140,13 +140,13 @@ describe("ImageDisplay", () => {
it("should call open function, when the container is clicked", () => {
const alternativeText = "alternative text";
- const { wrapper, urlProp, nameProp, open } = setup({
+ const { wrapper, src, nameProp, open } = setup({
isEditMode: false,
alternativeText,
});
const options: LightBoxOptions = {
- downloadUrl: urlProp,
- previewUrl: urlProp,
+ downloadUrl: src,
+ previewUrl: src,
alt: alternativeText,
name: nameProp,
};
@@ -160,13 +160,13 @@ describe("ImageDisplay", () => {
it("should call open function, when the image is clicked", () => {
const alternativeText = "alternative text";
- const { wrapper, urlProp, nameProp, open } = setup({
+ const { wrapper, src, nameProp, open } = setup({
isEditMode: false,
alternativeText,
});
const options: LightBoxOptions = {
- downloadUrl: urlProp,
- previewUrl: urlProp,
+ downloadUrl: src,
+ previewUrl: src,
alt: alternativeText,
name: nameProp,
};
@@ -180,13 +180,13 @@ describe("ImageDisplay", () => {
it("should call open function, when the overlay is clicked", async () => {
const alternativeText = "alternative text";
- const { wrapper, urlProp, nameProp, open } = setup({
+ const { wrapper, src, nameProp, open } = setup({
isEditMode: false,
alternativeText,
});
const options: LightBoxOptions = {
- downloadUrl: urlProp,
- previewUrl: urlProp,
+ downloadUrl: src,
+ previewUrl: src,
alt: alternativeText,
name: nameProp,
};
@@ -254,13 +254,13 @@ describe("ImageDisplay", () => {
it("should call open function, when Enter key is pressed", async () => {
const alternativeText = "alternative text";
- const { wrapper, urlProp, nameProp, open } = setup({
+ const { wrapper, src, nameProp, open } = setup({
isEditMode: false,
alternativeText,
});
const options: LightBoxOptions = {
- downloadUrl: urlProp,
- previewUrl: urlProp,
+ downloadUrl: src,
+ previewUrl: src,
alt: alternativeText,
name: nameProp,
};
@@ -275,13 +275,13 @@ describe("ImageDisplay", () => {
it("should call open function, when Space key is pressed", async () => {
const alternativeText = "alternative text";
- const { wrapper, urlProp, nameProp, open } = setup({
+ const { wrapper, src, nameProp, open } = setup({
isEditMode: false,
alternativeText,
});
const options: LightBoxOptions = {
- downloadUrl: urlProp,
- previewUrl: urlProp,
+ downloadUrl: src,
+ previewUrl: src,
alt: alternativeText,
name: nameProp,
};
@@ -332,11 +332,13 @@ describe("ImageDisplay", () => {
});
it("should have set src correctly", () => {
- const { wrapper, previewUrlProp } = setup({ isEditMode: true });
+ const { wrapper, previewSrc } = setup({
+ isEditMode: true,
+ });
const src = wrapper.find(imageSelektor).attributes("src");
- expect(src).toBe(previewUrlProp);
+ expect(src).toBe(previewSrc);
});
describe("When alternative text is defined", () => {
diff --git a/src/components/feature-board-file-element/content/display/image-display/ImageDisplay.vue b/src/components/feature-board-file-element/content/display/image-display/ImageDisplay.vue
index 5dfd591f64..8f03224b90 100644
--- a/src/components/feature-board-file-element/content/display/image-display/ImageDisplay.vue
+++ b/src/components/feature-board-file-element/content/display/image-display/ImageDisplay.vue
@@ -16,14 +16,13 @@
-
+
@@ -33,16 +32,18 @@ import { convertDownloadToPreviewUrl } from "@/utils/fileHelper";
import { I18N_KEY, injectStrict } from "@/utils/inject";
import { LightBoxOptions, useLightBox } from "@ui-light-box";
import { PropType, computed, defineComponent, ref } from "vue";
+import { ContentElementBar } from "@ui-board";
export default defineComponent({
name: "ImageDisplay",
props: {
- url: { type: String, required: true },
- previewUrl: { type: String, required: true },
+ src: { type: String, required: true },
+ previewSrc: { type: String, required: true },
name: { type: String, required: true },
isEditMode: { type: Boolean, required: true },
element: { type: Object as PropType, required: true },
},
+ components: { ContentElementBar },
setup(props) {
const i18n = injectStrict(I18N_KEY);
const containerRef = ref();
@@ -106,10 +107,10 @@ export default defineComponent({
};
const openLightBox = () => {
- const previewUrl = convertDownloadToPreviewUrl(props.url);
+ const previewUrl = convertDownloadToPreviewUrl(props.src);
const options: LightBoxOptions = {
- downloadUrl: props.url,
+ downloadUrl: props.src,
previewUrl: previewUrl,
alt: alternativeText.value,
name: props.name,
@@ -164,5 +165,6 @@ export default defineComponent({
.menu {
position: absolute;
top: 0px;
+ right: 0px;
}
diff --git a/src/components/feature-board-file-element/content/display/video-display/VideoDisplay.unit.ts b/src/components/feature-board-file-element/content/display/video-display/VideoDisplay.unit.ts
new file mode 100644
index 0000000000..388d78dd07
--- /dev/null
+++ b/src/components/feature-board-file-element/content/display/video-display/VideoDisplay.unit.ts
@@ -0,0 +1,64 @@
+import { mount } from "@vue/test-utils";
+import VideoDisplay from ".//VideoDisplay.vue";
+
+describe("VideoDisplay", () => {
+ const setup = () => {
+ document.body.setAttribute("data-app", "true");
+
+ const src = "test-source";
+ const name = "test-name";
+ const slotContent = "test-slot-content";
+ const propsData = {
+ src,
+ name,
+ };
+
+ const wrapper = mount(VideoDisplay, {
+ attachTo: document.body,
+ propsData,
+ slots: {
+ default: slotContent,
+ },
+ });
+
+ return {
+ wrapper,
+ src,
+ name,
+ };
+ };
+
+ it("should render video element", () => {
+ const { wrapper } = setup();
+
+ const video = wrapper.find("video");
+ expect(video.exists()).toBe(true);
+ });
+
+ it("should render video element with correct attributes", () => {
+ const { wrapper, src, name } = setup();
+
+ const video = wrapper.find("video");
+ expect(video.attributes("src")).toBe(src);
+ expect(video.attributes("alt")).toBe(name);
+ expect(video.attributes("controls")).toBe("controls");
+ expect(video.attributes("loading")).toBe("lazy");
+ });
+
+ it("should render with slot content", () => {
+ const { wrapper } = setup();
+
+ expect(wrapper.text()).toContain("test-slot-content");
+ });
+
+ describe("when video dispatches error event", () => {
+ it("should emit error event", () => {
+ const { wrapper } = setup();
+
+ const video = wrapper.find("video");
+ video.trigger("error");
+
+ expect(wrapper.emitted("error")).toBeTruthy();
+ });
+ });
+});
diff --git a/src/components/feature-board-file-element/content/display/video-display/VideoDisplay.vue b/src/components/feature-board-file-element/content/display/video-display/VideoDisplay.vue
new file mode 100644
index 0000000000..a67b187e27
--- /dev/null
+++ b/src/components/feature-board-file-element/content/display/video-display/VideoDisplay.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/feature-board-file-element/shared/types/FileAlert.enum.ts b/src/components/feature-board-file-element/shared/types/FileAlert.enum.ts
new file mode 100644
index 0000000000..9f9aab29d5
--- /dev/null
+++ b/src/components/feature-board-file-element/shared/types/FileAlert.enum.ts
@@ -0,0 +1,7 @@
+export enum FileAlert {
+ VIDEO_FORMAT_ERROR,
+ AWAITING_SCAN_STATUS,
+ SCAN_STATUS_WONT_CHECK,
+ SCAN_STATUS_BLOCKED,
+ SCAN_STATUS_ERROR,
+}
diff --git a/src/components/feature-board-file-element/shared/types/file-properties.ts b/src/components/feature-board-file-element/shared/types/file-properties.ts
index 98f903fc1f..4170a3b4bb 100644
--- a/src/components/feature-board-file-element/shared/types/file-properties.ts
+++ b/src/components/feature-board-file-element/shared/types/file-properties.ts
@@ -8,5 +8,6 @@ export interface FileProperties {
previewUrl?: string;
previewStatus: PreviewStatus;
isDownloadAllowed: boolean;
+ mimeType: string;
element: FileElementResponse;
}
diff --git a/src/components/organisms/administration/SchoolPolicy.unit.ts b/src/components/organisms/administration/SchoolPolicy.unit.ts
index 077dbf2309..713a85e9fd 100644
--- a/src/components/organisms/administration/SchoolPolicy.unit.ts
+++ b/src/components/organisms/administration/SchoolPolicy.unit.ts
@@ -2,6 +2,7 @@ import SchoolPolicy from "./SchoolPolicy.vue";
import AuthModule from "@/store/auth";
import SchoolsModule from "@/store/schools";
import PrivacyPolicyModule from "@/store/privacy-policy";
+import NotifierModule from "@/store/notifier";
import { createModuleMocks } from "@/utils/mock-store-module";
import { shallowMount, Wrapper } from "@vue/test-utils";
import createComponentMocks from "@@/tests/test-utils/componentMocks";
@@ -10,16 +11,20 @@ import { ConsentVersion } from "@/store/types/consent-version";
import {
AUTH_MODULE_KEY,
I18N_KEY,
+ NOTIFIER_MODULE_KEY,
PRIVACY_POLICY_MODULE_KEY,
SCHOOLS_MODULE_KEY,
} from "@/utils/inject";
-import Vue from "vue";
import { i18nMock } from "@@/tests/test-utils";
+import { downloadFile } from "@/utils/fileHelper";
+
+jest.mock("@/utils/fileHelper");
describe("SchoolPolicy", () => {
let authModule: jest.Mocked;
let schoolsModule: jest.Mocked;
let privacyPolicyModule: jest.Mocked;
+ let notifierModule: jest.Mocked;
const mockPolicy: ConsentVersion = {
_id: "123",
@@ -68,7 +73,9 @@ describe("SchoolPolicy", () => {
...getters,
});
- const wrapper: Wrapper = shallowMount(SchoolPolicy, {
+ notifierModule = createModuleMocks(NotifierModule);
+
+ const wrapper: Wrapper = shallowMount(SchoolPolicy, {
...createComponentMocks({
i18n: true,
}),
@@ -77,6 +84,7 @@ describe("SchoolPolicy", () => {
[PRIVACY_POLICY_MODULE_KEY.valueOf()]: privacyPolicyModule,
[AUTH_MODULE_KEY.valueOf()]: authModule,
[SCHOOLS_MODULE_KEY.valueOf()]: schoolsModule,
+ [NOTIFIER_MODULE_KEY.valueOf()]: notifierModule,
},
});
@@ -108,22 +116,20 @@ describe("SchoolPolicy", () => {
});
describe("when privacy policy is found", () => {
- it("should render download button", () => {
+ it("should render delete button", () => {
const wrapper = setup();
- expect(wrapper.find('[data-testid="download-button"]').exists()).toBe(
- true
- );
+ expect(wrapper.find('[data-testid="delete-button"]').exists()).toBe(true);
});
});
describe("when privacy policy is not found", () => {
- it("should not render download button", () => {
+ it("should not render delete button", () => {
const wrapper = setup({
getPrivacyPolicy: null,
});
- expect(wrapper.find('[data-testid="download-button"]').exists()).toBe(
+ expect(wrapper.find('[data-testid="delete-button"]').exists()).toBe(
false
);
});
@@ -161,9 +167,30 @@ describe("SchoolPolicy", () => {
it("should change isSchoolPolicyFormDialogOpen to true", () => {
const wrapper = setup();
- expect((wrapper.vm as any).isSchoolPolicyFormDialogOpen).toBe(false);
+ expect(wrapper.vm.isSchoolPolicyFormDialogOpen).toBe(false);
wrapper.find('[data-testid="edit-button"]').trigger("click");
- expect((wrapper.vm as any).isSchoolPolicyFormDialogOpen).toBe(true);
+ expect(wrapper.vm.isSchoolPolicyFormDialogOpen).toBe(true);
+ });
+ });
+
+ describe("when user clicks delete button", () => {
+ it("should change isDeletePolicyDialogOpen to true", () => {
+ const wrapper = setup();
+
+ expect(wrapper.vm.isDeletePolicyDialogOpen).toBe(false);
+ wrapper.find('[data-testid="delete-button"]').trigger("click");
+ expect(wrapper.vm.isDeletePolicyDialogOpen).toBe(true);
+ });
+ });
+
+ describe("when user clicks policy item", () => {
+ it("should call downloadFile method", () => {
+ const wrapper = setup();
+
+ const downloadFileMock = jest.mocked(downloadFile).mockReturnValueOnce();
+
+ wrapper.find('[data-testid="policy-item"]').vm.$emit("click");
+ expect(downloadFileMock).toHaveBeenCalledTimes(1);
});
});
diff --git a/src/components/organisms/administration/SchoolPolicy.vue b/src/components/organisms/administration/SchoolPolicy.vue
index d54e0c35e7..6fc10da73a 100644
--- a/src/components/organisms/administration/SchoolPolicy.vue
+++ b/src/components/organisms/administration/SchoolPolicy.vue
@@ -21,8 +21,17 @@
class="mb-6"
data-testid="progress-bar"
/>
-
-
+
+
$file_pdf_outline
@@ -33,9 +42,7 @@
{{
t("pages.administration.school.index.schoolPolicy.uploadedOn", {
- date: dayjs(privacyPolicy.publishedAt).format(
- t("format.dateTime")
- ),
+ date: formatDate(privacyPolicy.publishedAt),
})
}}
@@ -50,9 +57,8 @@
- $mdiPencilOutline
+ $mdiTrayArrowUp
- $mdiTrayArrowDown
+ $mdiTrashCanOutline
@@ -85,13 +90,34 @@
@close="closeDialog"
data-testid="form-dialog"
/>
+
+
+ {{ t("pages.administration.school.index.schoolPolicy.delete.title") }}
+
+
+
+
+ {{
+ t("pages.administration.school.index.schoolPolicy.delete.text")
+ }}
+
+
+
+
+
+
diff --git a/src/components/organisms/administration/SchoolPolicyFormDialog.unit.ts b/src/components/organisms/administration/SchoolPolicyFormDialog.unit.ts
index 84f5241a27..9db32125db 100644
--- a/src/components/organisms/administration/SchoolPolicyFormDialog.unit.ts
+++ b/src/components/organisms/administration/SchoolPolicyFormDialog.unit.ts
@@ -48,7 +48,7 @@ describe("SchoolPolicyFormDialog", () => {
notifierModule = createModuleMocks(NotifierModule);
- const wrapper: Wrapper = mount(
+ const wrapper: Wrapper = mount(
SchoolPolicyFormDialog as MountOptions,
{
...createComponentMocks({
@@ -72,13 +72,13 @@ describe("SchoolPolicyFormDialog", () => {
const wrapper = setup();
expect(
- wrapper.find('[data-testid="submit-button"]').attributes().disabled
+ wrapper.find('[data-testid="dialog-confirm"]').attributes().disabled
).toBeDefined();
});
it("should render warning icon", async () => {
const wrapper = setup();
- (wrapper.vm as any).isTouched = true;
+ wrapper.vm.isTouched = true;
await Vue.nextTick();
expect(wrapper.find('[data-testid="warning-icon"]').exists()).toBe(true);
});
@@ -88,7 +88,7 @@ describe("SchoolPolicyFormDialog", () => {
it("should emit 'close'", () => {
const wrapper = setup();
- wrapper.find('[data-testid="cancel-button"]').trigger("click");
+ wrapper.find('[data-testid="dialog-cancel"]').trigger("click");
expect(wrapper.emitted()).toHaveProperty("close");
});
});
diff --git a/src/components/organisms/administration/SchoolPolicyFormDialog.vue b/src/components/organisms/administration/SchoolPolicyFormDialog.vue
index 71bc4acab1..2299e194b2 100644
--- a/src/components/organisms/administration/SchoolPolicyFormDialog.vue
+++ b/src/components/organisms/administration/SchoolPolicyFormDialog.vue
@@ -1,12 +1,21 @@
-
+
{{ t("common.words.privacyPolicy") }}
-
+
-
+
{{
t(
"pages.administration.school.index.schoolPolicy.longText.willReplaceAndSendConsent"
@@ -43,32 +52,6 @@
>
-
-
-
-
- {{ t("pages.administration.school.index.schoolPolicy.cancel") }}
-
-
- $mdiFileReplaceOutline
- {{ t("pages.administration.school.index.schoolPolicy.replace") }}
-
-
-
@@ -184,7 +167,7 @@ export default defineComponent({
diff --git a/src/components/organisms/administration/SchoolTermsFormDialog.unit.ts b/src/components/organisms/administration/SchoolTermsFormDialog.unit.ts
index 28abfd76df..ade4a5632b 100644
--- a/src/components/organisms/administration/SchoolTermsFormDialog.unit.ts
+++ b/src/components/organisms/administration/SchoolTermsFormDialog.unit.ts
@@ -48,7 +48,7 @@ describe("SchoolPolicyFormDialog", () => {
notifierModule = createModuleMocks(NotifierModule);
- const wrapper: Wrapper
= mount(
+ const wrapper: Wrapper = mount(
SchoolTermsFormDialog as MountOptions,
{
...createComponentMocks({
@@ -72,13 +72,13 @@ describe("SchoolPolicyFormDialog", () => {
const wrapper = setup();
expect(
- wrapper.find('[data-testid="submit-button"]').attributes().disabled
+ wrapper.find('[data-testid="dialog-confirm"]').attributes().disabled
).toBeDefined();
});
it("should render warning icon", async () => {
const wrapper = setup();
- (wrapper.vm as any).isTouched = true;
+ wrapper.vm.isTouched = true;
await Vue.nextTick();
expect(wrapper.find('[data-testid="warning-icon"]').exists()).toBe(true);
});
@@ -88,7 +88,7 @@ describe("SchoolPolicyFormDialog", () => {
it("should emit 'close'", () => {
const wrapper = setup();
- wrapper.find('[data-testid="cancel-button"]').trigger("click");
+ wrapper.find('[data-testid="dialog-cancel"]').trigger("click");
expect(wrapper.emitted()).toHaveProperty("close");
});
});
diff --git a/src/components/organisms/administration/SchoolTermsFormDialog.vue b/src/components/organisms/administration/SchoolTermsFormDialog.vue
index eac46ceec6..d9ded71d67 100644
--- a/src/components/organisms/administration/SchoolTermsFormDialog.vue
+++ b/src/components/organisms/administration/SchoolTermsFormDialog.vue
@@ -1,12 +1,21 @@
-
+
{{ t("common.words.termsOfUse") }}
-
+
-
+
{{
t(
"pages.administration.school.index.termsOfUse.longText.willReplaceAndSendConsent"
@@ -41,32 +50,6 @@
>
-
-
-
-
- {{ t("pages.administration.school.index.termsOfUse.cancel") }}
-
-
- $mdiFileReplaceOutline
- {{ t("pages.administration.school.index.termsOfUse.replace") }}
-
-
-
@@ -180,7 +163,7 @@ export default defineComponent({