diff --git a/alfa-client/apps/admin-e2e/docker-compose.yml b/alfa-client/apps/admin-e2e/docker-compose.yml index 11682ebeb086ca5740d4da1b5c3c2feaa34aaee1..edc5dc8b2f98963025b2521c960a87a29efc6efb 100644 --- a/alfa-client/apps/admin-e2e/docker-compose.yml +++ b/alfa-client/apps/admin-e2e/docker-compose.yml @@ -79,7 +79,7 @@ services: depends_on: - user-manager extra_hosts: - - "host.docker.internal:host-gateway" + - 'host.docker.internal:host-gateway' alfa-cors-proxy: image: alfa-cors-proxy @@ -91,7 +91,6 @@ services: alfa: condition: service_started - user-manager: image: docker.ozg-sh.de/user-manager:${USER_MANAGER_DOCKER_IMAGE:-snapshot-latest} platform: linux/amd64 @@ -145,4 +144,4 @@ services: user-manager: condition: service_started extra_hosts: - - "host.docker.internal:host-gateway" + - 'host.docker.internal:host-gateway' diff --git a/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts index 4c022605190076fae6eba31464aaf72e4018c62e..7cf7de163ef2166b97814f4151a4d787bcc6c894 100644 --- a/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts +++ b/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts @@ -24,14 +24,20 @@ import 'cypress-real-events'; import { convertToDataTestId } from '../../support/tech-util'; +//TODO BenutzerListPage erstellen welche den Button und die Liste enthaelt. export class BenutzerListE2EComponent { private readonly headline: string = 'user-list-headline'; + private readonly list: string = 'user-list'; private readonly benutzerHinzufuegenButton: string = 'add-user-button'; public getHeadline(): Cypress.Chainable<Element> { return cy.getTestElement(this.headline); } + public getList(): Cypress.Chainable<Element> { + return cy.getTestElement(this.list); + } + public getHinzufuegenButton(): Cypress.Chainable<Element> { return cy.getTestElement(this.benutzerHinzufuegenButton); } @@ -103,6 +109,11 @@ export class BenutzerE2EComponent { private readonly organisationsEinheitCheckboxSuffix: string = '-checkbox-editor'; private readonly saveButton: string = 'save-button'; + private readonly deleteButton: string = 'delete-button'; + + public getHeadline(): Cypress.Chainable<Element> { + return cy.getTestElement(this.headline); + } public getHeadline(): Cypress.Chainable<Element> { return cy.getTestElement(this.headline); @@ -144,11 +155,28 @@ export class BenutzerE2EComponent { return cy.getTestElement(this.datenbeauftragungCheckbox); } + public getOrganisationsEinheitCheckbox(einheit: string): Cypress.Chainable<Element> { + return cy.getTestElement(einheit + this.organisationsEinheitCheckboxSuffix); + } + public getSaveButton(): Cypress.Chainable<Element> { return cy.getTestElement(this.saveButton); } - public getOrganisationsEinheitCheckbox(einheit: string): Cypress.Chainable<Element> { - return cy.getTestElement(einheit + this.organisationsEinheitCheckboxSuffix); + public getDeleteButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.deleteButton); + } +} + +export class BenutzerDeleteDialogE2EComponent { + private readonly deleteButton: string = 'dialog-delete'; + private readonly cancelButton: string = 'cancel-dialog'; + + public getCancelButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.cancelButton); + } + + public getDeleteButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.deleteButton); } } diff --git a/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component.ts index bfdf6b624d856e9b1a165f8984e739deccdc4a80..ac3917c2dd21ca7859c009a824267033c8df5349 100644 --- a/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component.ts +++ b/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component.ts @@ -21,21 +21,52 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +export class OrganisationsEinheitListE2EComponent { + private readonly root: string = 'organisations-einheit-list'; + + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); + } + + public getListItem(name: string): OrganisationsEinheitListItemE2EComponent { + return new OrganisationsEinheitListItemE2EComponent(name); + } +} + +export class OrganisationsEinheitListItemE2EComponent { + private root: string; -export class OrganisationsEinheitenE2EComponent { - private readonly organisationsEinheitenList: string = 'organisations-einheit-list'; - private readonly organisationsEinheitHinzufuegen: string = 'add-organisationseinheit-button'; private readonly organisationsEinheitItemSuffix: string = '-organisation-item'; + private readonly deleteButton: string = 'delete-button'; + + constructor(name: string) { + this.root = name; + } + + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root + this.organisationsEinheitItemSuffix); + } + + public getDeleteButton(): Cypress.Chainable<Element> { + return this.getRoot().findTestElementWithClass(this.deleteButton); + } +} + +export class OrganisationsEinheitDeleteDialogE2EComponent { + private readonly root: string = 'organisations-einheit-delete-dialog'; + + private readonly deleteButton: string = 'dialog-delete-button'; + private readonly cancelButton: string = 'dialog-cancel-button'; - public getOrganisationsEinheitList(): Cypress.Chainable<Element> { - return cy.getTestElement(this.organisationsEinheitenList); + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); } - public getOrganisationsEinheitHinzufuegenButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.organisationsEinheitHinzufuegen); + public getDeleteButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.deleteButton); } - public getListItem(name: string) { - return cy.getTestElement(name + this.organisationsEinheitItemSuffix); + public getCancelButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.cancelButton); } } diff --git a/alfa-client/apps/admin-e2e/src/components/user-profile/current-user-profile.component.e2e.ts b/alfa-client/apps/admin-e2e/src/components/user-profile/current-user-profile.component.e2e.ts index b96370d68b65cf7848e13b6eb5807473aadb40a3..effa3edb707cf24d98306dc972fefae6770b154e 100644 --- a/alfa-client/apps/admin-e2e/src/components/user-profile/current-user-profile.component.e2e.ts +++ b/alfa-client/apps/admin-e2e/src/components/user-profile/current-user-profile.component.e2e.ts @@ -26,6 +26,7 @@ import { UserProfileE2EComponent } from './user-profile.component.e2e'; export class CurrentUserProfileE2EComponent { private readonly locatorUserIconButton: string = 'popup-button-content'; private readonly locatorLogoutButton: string = 'popup-logout-button'; + private readonly locatorDocumentation: string = 'admin-documentation'; private readonly locatorRoot: string = 'current-user'; @@ -46,7 +47,11 @@ export class CurrentUserProfileE2EComponent { return cy.getTestElement(this.locatorUserIconButton); } - private getLogoutButton() { + public getLogoutButton() { return cy.getTestElement(this.locatorLogoutButton); } + + public getDocumentation() { + return cy.getTestElement(this.locatorDocumentation); + } } diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/app/user-profile-menu.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/app/user-profile-menu.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..19852a52775d1f104314441a3a85ce466a7df7f0 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/app/user-profile-menu.cy.ts @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import { getUrl } from '@ngxp/rest'; +import { HttpMethodE2E } from '../../../model/util'; +import { HeaderE2EComponent } from '../../../page-objects/header.po'; +import { MainPage } from '../../../page-objects/main.po'; +import { getBaseUrl, intercept, waitOfInterceptor } from '../../../support/cypress-helper'; +import { exist, shouldHaveAttribute } from '../../../support/cypress.util'; +import { ApiRootLinkRelE2E } from '../../../support/linkrels'; +import { loginAsAriane } from '../../../support/user-util'; + +describe('User Profile Menu', () => { + const mainPage: MainPage = new MainPage(); + const header: HeaderE2EComponent = mainPage.getHeader(); + + let documentationLink: string = ''; + + before(() => { + const interceptor: string = 'getApiRoot'; + intercept(HttpMethodE2E.GET, `${getBaseUrl()}/api`).as(interceptor); + loginAsAriane(); + waitOfInterceptor(interceptor).then( + (interception) => (documentationLink = getUrl(interception?.response?.body, ApiRootLinkRelE2E.DOCUMENTATIONS)), + ); + }); + + describe('open user profile menu', () => { + before(() => { + header.getCurrentUserProfile().getUserIconButton().click(); + }); + + it('should show logout button', () => { + exist(header.getCurrentUserProfile().getLogoutButton()); + }); + + it('should show documentation', () => { + exist(header.getCurrentUserProfile().getDocumentation()); + }); + + it('should find documentation link', () => { + shouldHaveAttribute(header.getCurrentUserProfile().getDocumentation().find('a'), 'href', documentationLink); + }); + }); +}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-loesche.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-loesche.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd494a41834116ef96c2ed992e026fba95bb53ef --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-loesche.cy.ts @@ -0,0 +1,33 @@ +import { faker } from '@faker-js/faker'; +import { E2EBenutzerHelper } from 'apps/admin-e2e/src/helper/benutzer/benutzer.helper'; +import { E2EBenutzerVerifier } from 'apps/admin-e2e/src/helper/benutzer/benutzer.verifier'; +import { AdminUserE2E } from 'apps/admin-e2e/src/model/util'; +import { loginAsAriane } from 'apps/admin-e2e/src/support/user-util'; + +describe('Benutzer Löschen', () => { + const benutzerVerifier: E2EBenutzerVerifier = new E2EBenutzerVerifier(); + const benutzerHelper: E2EBenutzerHelper = new E2EBenutzerHelper(); + + const userName: string = 'testtheo' + faker.string.uuid(); + const user: AdminUserE2E = { + vorname: 'Theo', + nachname: 'Testuser', + username: userName, + email: 'theo' + faker.string.uuid() + '@ozg-sh.de', + isUser: true, + organisationseinheiten: [], + }; + + before(() => { + loginAsAriane(); + }); + + it('should delete user', () => { + benutzerHelper.openNewBenutzerPage(); + benutzerHelper.addBenutzerAndSave(user); + + benutzerHelper.deleteBenutzer(userName); + + benutzerVerifier.verifyUserNotInList(userName); + }); +}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheit.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheit.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f52b488067ab890200384cfe2d0eba448c87078 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheit.cy.ts @@ -0,0 +1,53 @@ +import { E2EOrganisationsEinheitHelper } from 'apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper'; +import { E2EOrganisationsEinheitVerifier } from 'apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.verifier'; +import { OrganisationsEinheitPage } from 'apps/admin-e2e/src/page-objects/organisations-einheit.po'; +import { ZustaendigeStelleDialogE2EComponent } from '../../../components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component'; +import { exist } from '../../../support/cypress.util'; +import { loginAsAriane } from '../../../support/user-util'; + +describe('Organisationseinheit', () => { + const organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + + const organisationsEinheitHelper: E2EOrganisationsEinheitHelper = new E2EOrganisationsEinheitHelper(); + const organisationsEinheitVerifier: E2EOrganisationsEinheitVerifier = new E2EOrganisationsEinheitVerifier(); + + const zustaendigeStelleSearchComponent: ZustaendigeStelleDialogE2EComponent = new ZustaendigeStelleDialogE2EComponent(); + + const organisationsEinheit: string = 'Wasserwerk - Hamburg Wasser - Hamburger Stadtentwässerung'; + + before(() => { + loginAsAriane(); + }); + + describe('hinzufügen', () => { + it('should show search dialog on add button click', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + organisationsEinheitPage.getAddButton().click(); + + exist(zustaendigeStelleSearchComponent.getZustaendigeStelleForm()); + }); + + it('should find at least one organisationseinheit on search', () => { + zustaendigeStelleSearchComponent.enterSearchTerm(organisationsEinheit); + + zustaendigeStelleSearchComponent.expectNumberOfEntriesToBeGreaterThan(1); + }); + + it('should show organisationseinheit in list', () => { + organisationsEinheitHelper.addOrganisationsEinheit(organisationsEinheit); + + organisationsEinheitVerifier.verifyOrganisationsEinheitInList(organisationsEinheit); + }); + }); + + describe('löschen', () => { + it('should not show entry in list', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + organisationsEinheitHelper.deleteOrganisationsEinheit(organisationsEinheit); + + organisationsEinheitVerifier.verifyOrganisationsEinheitNotInList(organisationsEinheit); + }); + }); +}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheiten-page.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheiten-page.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..c17724c86bb9ed02eacc5f5f88bbcf35e865b79a --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheiten-page.cy.ts @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import { OrganisationsEinheitListE2EComponent } from 'apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { E2EOrganisationsEinheitHelper } from 'apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper'; +import { OrganisationsEinheitE2E } from 'apps/admin-e2e/src/model/organisations-einheit'; +import { OrganisationsEinheitPage } from 'apps/admin-e2e/src/page-objects/organisations-einheit.po'; +import { exist } from '../../../support/cypress.util'; +import { loginAsAriane } from '../../../support/user-util'; + +describe('Organisationsheiten page', () => { + const organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + + const organisationsEinheitHelper: E2EOrganisationsEinheitHelper = new E2EOrganisationsEinheitHelper(); + + const organisationsEinheitList: OrganisationsEinheitListE2EComponent = new OrganisationsEinheitListE2EComponent(); + + before(() => { + loginAsAriane(); + }); + + it('should show list', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + exist(organisationsEinheitPage.getList().getRoot()); + }); + + it('should show add button', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + exist(organisationsEinheitPage.getAddButton()); + }); + + it('should show default (Bauamt, Fundstelle, Denkmalpflege) entries', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + exist(organisationsEinheitList.getListItem(OrganisationsEinheitE2E.BAUAMT).getRoot()); + exist(organisationsEinheitList.getListItem(OrganisationsEinheitE2E.FUNDSTELLE).getRoot()); + exist(organisationsEinheitList.getListItem(OrganisationsEinheitE2E.DENKMALPFLEGE).getRoot()); + }); +}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-hinzufuegen.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-hinzufuegen.cy.ts deleted file mode 100644 index ce01dec8af3d1dcb9404b260266af663b754b63e..0000000000000000000000000000000000000000 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-hinzufuegen.cy.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { convertToDataTestId } from 'apps/admin-e2e/src/support/tech-util'; -import { OrganisationsEinheitenE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten.e2e.component'; -import { ZustaendigeStelleDialogE2EComponent } from '../../../components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component'; -import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; -import { exist } from '../../../support/cypress.util'; -import { loginAsAriane } from '../../../support/user-util'; - -describe('Organisationseinheiten', () => { - const mainPage: MainPage = new MainPage(); - const organisationsEinheitenComponent: OrganisationsEinheitenE2EComponent = new OrganisationsEinheitenE2EComponent(); - const zustaendigeStelleSearchComponent: ZustaendigeStelleDialogE2EComponent = new ZustaendigeStelleDialogE2EComponent(); - - const searchTerm: string = 'Hamburg'; - - before(() => { - loginAsAriane(); - }); - - it('should show table with Organisationseinheiten', () => { - waitForSpinnerToDisappear(); - mainPage.getOrganisationEinheitNavigationItem().click(); - - exist(organisationsEinheitenComponent.getOrganisationsEinheitHinzufuegenButton()); - }); - - it('should show button to add Organisationseinheit', () => { - exist(organisationsEinheitenComponent.getOrganisationsEinheitHinzufuegenButton()); - }); - - it('should show search Organisationseinheit dialog', () => { - organisationsEinheitenComponent.getOrganisationsEinheitHinzufuegenButton().click(); - - exist(zustaendigeStelleSearchComponent.getZustaendigeStelleForm()); - }); - - it('should find at least one Organisationseinheit', () => { - zustaendigeStelleSearchComponent.enterSearchTerm(searchTerm); - - zustaendigeStelleSearchComponent.expectNumberOfEntriesToBeGreaterThan(1); - }); - - it('should add first Organisationseinheit', () => { - zustaendigeStelleSearchComponent.getZustaendigeStelleTitle(0).then((name: string) => { - zustaendigeStelleSearchComponent.clickFoundItem(0); - exist(organisationsEinheitenComponent.getListItem(convertToDataTestId(name))); - }); - }); -}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts deleted file mode 100644 index e65e64480cdbff481e8939cd1e79187fc84efd37..0000000000000000000000000000000000000000 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { OrganisationsEinheitenE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten.e2e.component'; -import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; -import { exist } from '../../../support/cypress.util'; -import { loginAsAriane } from '../../../support/user-util'; - -describe('Organisationsheiten list', () => { - const mainPage: MainPage = new MainPage(); - const organisationsEinheitenTab: OrganisationsEinheitenE2EComponent = new OrganisationsEinheitenE2EComponent(); - - before(() => { - loginAsAriane(); - }); - - it('should show Organisationseinheiten list', () => { - waitForSpinnerToDisappear(); - mainPage.getOrganisationEinheitNavigationItem().click(); - waitForSpinnerToDisappear(); - - exist(organisationsEinheitenTab.getOrganisationsEinheitList()); - }); - - it('should show entry for Bauamt', () => { - exist(organisationsEinheitenTab.getListItem('Bauamt')); - }); - - it('should show entry for Fundstelle', () => { - exist(organisationsEinheitenTab.getListItem('Fundstelle')); - }); - - it('should show entry for Denkmalpflege', () => { - exist(organisationsEinheitenTab.getListItem('Denkmalpflege')); - }); -}); diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts index 9c8898bc0fcddef20b538919cc20bde8bc6e1993..320db78e13e0fee0cae8dad0cd94969ad7b0e2de 100644 --- a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts +++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts @@ -1,4 +1,8 @@ -import { BenutzerE2EComponent } from '../../components/benutzer/benutzer.e2e.component'; +import { + BenutzerDeleteDialogE2EComponent, + BenutzerE2EComponent, + BenutzerListE2EComponent, +} from '../../components/benutzer/benutzer.e2e.component'; import { SnackBarE2EComponent } from '../../components/ui/snackbar.e2e.component'; import { OrganisationsEinheitE2E } from '../../model/organisations-einheit'; import { AdminUserE2E } from '../../model/util'; @@ -7,6 +11,8 @@ import { exist, notExist } from '../../support/cypress.util'; export class E2EBenutzerExecutor { private benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); private snackBar: SnackBarE2EComponent = new SnackBarE2EComponent(); + private benutzerDeleteDialog: BenutzerDeleteDialogE2EComponent = new BenutzerDeleteDialogE2EComponent(); + private benutzerListPage: BenutzerListE2EComponent = new BenutzerListE2EComponent(); public modifyBenutzer(user: AdminUserE2E): void { this.benutzerPage.getVornameInput().type(user.vorname); @@ -39,9 +45,17 @@ export class E2EBenutzerExecutor { exist(this.snackBar.getMessage()); this.snackBar.getCloseButton().click(); notExist(this.snackBar.getMessage()); + exist(this.benutzerListPage.getList()); } public saveBenutzer(): void { this.benutzerPage.getSaveButton().click(); } + + public deleteBenutzer(): void { + this.benutzerPage.getDeleteButton().click(); + exist(this.benutzerDeleteDialog.getDeleteButton()); + this.benutzerDeleteDialog.getDeleteButton().click(); + exist(this.benutzerListPage.getList()); + } } diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts index 3f2b6be7847116296225a8c28d81fb86e37f9cf9..2b06766322f460cdb12f6ec065e19b9e79e756f0 100644 --- a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts +++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts @@ -17,32 +17,45 @@ export class E2EBenutzerHelper { public addBenutzerAndSave(user: AdminUserE2E): void { this.addBenutzer(user); - this.executer.saveAndCloseSnackbar(); + this.saveAndCloseSnackbar(); } public addBenutzer(user: AdminUserE2E): void { - this.executer.modifyBenutzer(user); - } - - public openBenutzerPage(userName: string): void { - this.navigator.openBenutzerPage(userName); + this.modifyBenutzer(user); } public editBenutzerAndSave(user: AdminUserE2E): void { this.editBenutzer(user); - this.executer.saveAndCloseSnackbar(); + this.saveAndCloseSnackbar(); } public editBenutzer(user: AdminUserE2E): void { + this.modifyBenutzer(user); + } + + private modifyBenutzer(user: AdminUserE2E): void { this.executer.modifyBenutzer(user); } public editOrganisationsEinheitenAndSave(organisationsEinheiten: OrganisationsEinheitE2E[]): void { this.executer.modifyOrganisationsEinheiten(organisationsEinheiten); + this.saveAndCloseSnackbar(); + } + + private saveAndCloseSnackbar(): void { this.executer.saveAndCloseSnackbar(); } public saveBenutzer(): void { this.executer.saveBenutzer(); } + + public deleteBenutzer(userName: string): void { + this.openBenutzerPage(userName); + this.executer.deleteBenutzer(); + } + + public openBenutzerPage(userName: string): void { + this.navigator.openBenutzerPage(userName); + } } diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts index d0c33452341f51de81cefee2d0f7f0e610532c7b..be750e3dfbfe90c3821451932ecb475bd31cbbe5 100644 --- a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts +++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts @@ -4,7 +4,7 @@ import { BenutzerListItemE2EComponent, } from '../../components/benutzer/benutzer.e2e.component'; import { AdminUserE2E } from '../../model/util'; -import { contains, exist } from '../../support/cypress.util'; +import { contains, exist, notExist } from '../../support/cypress.util'; import { AlfaRollen } from '../../support/user-util'; export class E2EBenutzerVerifier { @@ -41,6 +41,10 @@ export class E2EBenutzerVerifier { if (user.isAdmin) contains(benutzer.getRoles(), AlfaRollen.ADMIN); } + public verifyUserNotInList(userName: string): void { + notExist(this.getBenutzerItem(userName).getRoot()); + } + private getBenutzerItem(userName: string): BenutzerListItemE2EComponent { return this.benutzerListPage.getItem(userName); } diff --git a/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.executor.ts b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.executor.ts new file mode 100644 index 0000000000000000000000000000000000000000..a89ad1f958ca17a73511980431455f6e8ae479bc --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.executor.ts @@ -0,0 +1,34 @@ +import { + OrganisationsEinheitDeleteDialogE2EComponent, + OrganisationsEinheitListE2EComponent, +} from '../../components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { ZustaendigeStelleDialogE2EComponent } from '../../components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component'; +import { OrganisationsEinheitPage } from '../../page-objects/organisations-einheit.po'; +import { exist } from '../../support/cypress.util'; +import { convertToDataTestId } from '../../support/tech-util'; + +export class E2EOrganisationsEinheitExecutor { + private readonly organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + private readonly organisationsEinheitList: OrganisationsEinheitListE2EComponent = this.organisationsEinheitPage.getList(); + + private readonly deleteDialog: OrganisationsEinheitDeleteDialogE2EComponent = + new OrganisationsEinheitDeleteDialogE2EComponent(); + + private readonly zustaendigeStelleSearchComponent: ZustaendigeStelleDialogE2EComponent = + new ZustaendigeStelleDialogE2EComponent(); + + public addOrganisationsEinheit(name: string): void { + //TODO von index auf Name umstellen + this.zustaendigeStelleSearchComponent + .getZustaendigeStelleTitle(0) + .then((name: string) => this.zustaendigeStelleSearchComponent.clickFoundItem(0)); + exist(this.organisationsEinheitList.getRoot()); + } + + public deleteOrganisationsEinheit(name: string): void { + this.organisationsEinheitList.getListItem(convertToDataTestId(name)).getDeleteButton().click(); + exist(this.deleteDialog.getRoot()); + this.deleteDialog.getDeleteButton().click(); + exist(this.organisationsEinheitList.getRoot()); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper.ts b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..339793cde888c3cedcbd1dc6e4dd3b7a7f28e313 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper.ts @@ -0,0 +1,19 @@ +import { E2EOrganisationsEinheitExecutor } from './organisations-einheit.executor'; +import { E2EOrganisationsEinheitNavigator } from './organisations-einheit.navigator'; + +export class E2EOrganisationsEinheitHelper { + private readonly navigator: E2EOrganisationsEinheitNavigator = new E2EOrganisationsEinheitNavigator(); + private readonly executor: E2EOrganisationsEinheitExecutor = new E2EOrganisationsEinheitExecutor(); + + public openOrganisationsEinheitPage(): void { + this.navigator.openOrganisationsEinheitListPage(); + } + + public addOrganisationsEinheit(name: string): void { + this.executor.addOrganisationsEinheit(name); + } + + public deleteOrganisationsEinheit(name: string): void { + this.executor.deleteOrganisationsEinheit(name); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.navigator.ts b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.navigator.ts new file mode 100644 index 0000000000000000000000000000000000000000..6274ee80d4bbbf2d2326f685d83d84da6e1ea07e --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.navigator.ts @@ -0,0 +1,21 @@ +import { OrganisationsEinheitListE2EComponent } from '../../components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { MainPage } from '../../page-objects/main.po'; +import { OrganisationsEinheitPage } from '../../page-objects/organisations-einheit.po'; +import { exist } from '../../support/cypress.util'; + +export class E2EOrganisationsEinheitNavigator { + private readonly mainPage: MainPage = new MainPage(); + + private readonly organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + private readonly organisationsEinheitList: OrganisationsEinheitListE2EComponent = this.organisationsEinheitPage.getList(); + + public openOrganisationsEinheitListPage(): void { + this.navigateToDomain(); + this.mainPage.getOrganisationEinheitNavigationItem().click(); + exist(this.organisationsEinheitList.getRoot()); + } + + private navigateToDomain(): void { + this.mainPage.getHeader().getLogo().click(); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.verifier.ts b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.verifier.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb1b43021649ffea97c252237556966d3dea38ac --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.verifier.ts @@ -0,0 +1,17 @@ +import { OrganisationsEinheitListE2EComponent } from '../../components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { OrganisationsEinheitPage } from '../../page-objects/organisations-einheit.po'; +import { exist, notExist } from '../../support/cypress.util'; +import { convertToDataTestId } from '../../support/tech-util'; + +export class E2EOrganisationsEinheitVerifier { + private readonly organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + private readonly organisationsEinheitList: OrganisationsEinheitListE2EComponent = this.organisationsEinheitPage.getList(); + + public verifyOrganisationsEinheitInList(name: string): void { + exist(this.organisationsEinheitList.getListItem(convertToDataTestId(name)).getRoot()); + } + + public verifyOrganisationsEinheitNotInList(name: string): void { + notExist(this.organisationsEinheitList.getListItem(convertToDataTestId(name)).getRoot()); + } +} diff --git a/alfa-client/apps/admin-e2e/src/model/organisations-einheit.ts b/alfa-client/apps/admin-e2e/src/model/organisations-einheit.ts index 9870f7e3f1292df175cd154c88c59fbd60a94f26..311c1bfd5c4f39e55d5ddf17a4f91fdf34f19067 100644 --- a/alfa-client/apps/admin-e2e/src/model/organisations-einheit.ts +++ b/alfa-client/apps/admin-e2e/src/model/organisations-einheit.ts @@ -1,5 +1,7 @@ export enum OrganisationsEinheitE2E { - ORDNUNGSAMT = 'Ordnungsamt', + BAUAMT = 'Bauamt', DENKMALPFLEGE = 'Denkmalpflege', + FUNDSTELLE = 'Fundstelle', + ORDNUNGSAMT = 'Ordnungsamt', WIRTSCHAFTSFOERDERUNG = 'Wirtschaftsförderung', } diff --git a/alfa-client/apps/admin-e2e/src/page-objects/header.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/header.po.ts index fb87e566cab2f09fd01b593fa883841fc6075243..60fc24b0d4bf09419e0f0e0b0e15f3b439d2f500 100644 --- a/alfa-client/apps/admin-e2e/src/page-objects/header.po.ts +++ b/alfa-client/apps/admin-e2e/src/page-objects/header.po.ts @@ -24,13 +24,13 @@ import { CurrentUserProfileE2EComponent } from '../components/user-profile/current-user-profile.component.e2e'; import { UserSettingsE2EComponent } from '../components/user-settings/user-settings.component.e2e'; +//TODO Zu den Componenten packen, nicht zu den page-objects export class HeaderE2EComponent { private readonly locatorLogo: string = 'logo-link'; private readonly locatorRoot: string = 'header'; private readonly userSettings: UserSettingsE2EComponent = new UserSettingsE2EComponent(); - private readonly currentUserProfile: CurrentUserProfileE2EComponent = - new CurrentUserProfileE2EComponent(); + private readonly currentUserProfile: CurrentUserProfileE2EComponent = new CurrentUserProfileE2EComponent(); public getRoot() { return cy.getTestElement(this.locatorRoot); diff --git a/alfa-client/apps/admin-e2e/src/page-objects/login.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/login.po.ts index 73005caaf69cb0be2990541fdabe003326a20153..48a03c246ceeeea3b8b7e58c3f211e091c86e955 100644 --- a/alfa-client/apps/admin-e2e/src/page-objects/login.po.ts +++ b/alfa-client/apps/admin-e2e/src/page-objects/login.po.ts @@ -1,3 +1,4 @@ +//TODO Das sollte eher eine Component als eine Page sein export class LoginPage { private readonly locatorLogin: string = '#kc-login'; private readonly locatorBarrierefreiheitLink: string = '#kc-barrierefreiheit'; diff --git a/alfa-client/apps/admin-e2e/src/page-objects/organisations-einheit.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/organisations-einheit.po.ts new file mode 100644 index 0000000000000000000000000000000000000000..b37d9a9d3f0cd1cafc518f2ae995b0988807c7e9 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/page-objects/organisations-einheit.po.ts @@ -0,0 +1,14 @@ +import { OrganisationsEinheitListE2EComponent } from '../components/organisationseinheiten/organisationseinheiten.e2e.component'; + +export class OrganisationsEinheitPage { + private readonly addButton: string = 'add-organisationseinheit-button'; + private readonly list: OrganisationsEinheitListE2EComponent = new OrganisationsEinheitListE2EComponent(); + + public getAddButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.addButton); + } + + public getList(): OrganisationsEinheitListE2EComponent { + return this.list; + } +} diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.scss b/alfa-client/apps/admin-e2e/src/support/linkrels.ts similarity index 92% rename from alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.scss rename to alfa-client/apps/admin-e2e/src/support/linkrels.ts index 54c4f3eb8c92af93694c03cdf577fed23cf9f86b..4891aa61e1bdf2e6bcc77f167e7aa6a7fd7aedb1 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.scss +++ b/alfa-client/apps/admin-e2e/src/support/linkrels.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei @@ -21,3 +21,6 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +export enum ApiRootLinkRelE2E { + DOCUMENTATIONS = 'documentations', +} diff --git a/alfa-client/apps/admin/src/app/app.component.html b/alfa-client/apps/admin/src/app/app.component.html index 8302d53018783e91ffb01539a627b9e5ee7730ff..55e1c8824521c5c846932ee5d648172d86466705 100644 --- a/alfa-client/apps/admin/src/app/app.component.html +++ b/alfa-client/apps/admin/src/app/app.component.html @@ -36,7 +36,10 @@ > <ods-admin-logo-icon /> </a> - <user-profile-button-container data-test-id="user-profile-button"></user-profile-button-container> + <user-profile-button-container + [apiRootStateResource]="apiRootStateResource$ | async" + data-test-id="user-profile-button" + ></user-profile-button-container> </header> <div class="flex h-screen w-full justify-center overflow-y-auto"> <ods-navbar data-test-id="navigation"> diff --git a/alfa-client/apps/admin/src/app/app.component.spec.ts b/alfa-client/apps/admin/src/app/app.component.spec.ts index be4c62dc1e47ce960e24207304455bfdf963362a..baa8c7fcc532a0e859fe596782a94bf7b2257803 100644 --- a/alfa-client/apps/admin/src/app/app.component.spec.ts +++ b/alfa-client/apps/admin/src/app/app.component.spec.ts @@ -27,31 +27,18 @@ import { KeycloakTokenService } from '@admin/keycloak-shared'; import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared'; import { BuildInfoComponent } from '@alfa-client/common'; import { createEmptyStateResource, createStateResource, HasLinkPipe } from '@alfa-client/tech-shared'; -import { - existsAsHtmlElement, - getElementComponentFromFixtureByCss, - Mock, - mock, - notExistsAsHtmlElement, -} from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getElementComponentFromFixtureByCss, Mock, mock, notExistsAsHtmlElement, } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { AuthenticationService } from '@authentication'; -import { - AdminLogoIconComponent, - MailboxIconComponent, - NavbarComponent, - NavItemComponent, - OrgaUnitIconComponent, - UsersIconComponent, -} from '@ods/system'; +import { AdminLogoIconComponent, MailboxIconComponent, NavbarComponent, NavItemComponent, OrgaUnitIconComponent, UsersIconComponent, } from '@ods/system'; import { createConfigurationResource } from 'libs/admin/configuration-shared/test/configuration'; import { MenuContainerComponent } from 'libs/admin/configuration/src/lib/menu-container/menu-container.component'; import { createApiRootResource } from 'libs/api-root-shared/test/api-root'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent, MockDirective } from 'ng-mocks'; import { of, Subscription } from 'rxjs'; -import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component'; +import { UserProfileButtonContainerComponent } from '../../../../libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component'; import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component'; import { AppComponent } from './app.component'; diff --git a/alfa-client/apps/admin/src/app/app.component.ts b/alfa-client/apps/admin/src/app/app.component.ts index e348ba876d663be3430f1d5f5e941fa96f524ae6..909b6cd198bd3ccb91dd61e922abb6a1ed79210c 100644 --- a/alfa-client/apps/admin/src/app/app.component.ts +++ b/alfa-client/apps/admin/src/app/app.component.ts @@ -35,7 +35,7 @@ import { AuthenticationService } from '@authentication'; import { hasLink } from '@ngxp/rest'; import { AdminLogoIconComponent, NavbarComponent, NavItemComponent, OrgaUnitIconComponent, UsersIconComponent, } from '@ods/system'; import { filter, Observable, Subscription } from 'rxjs'; -import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component'; +import { UserProfileButtonContainerComponent } from '../../../../libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component'; import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component'; @Component({ diff --git a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.spec.ts b/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.spec.ts deleted file mode 100644 index b40a661442d6728f6c7819a0548e556d615197a8..0000000000000000000000000000000000000000 --- a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { dispatchEventFromFixture, getElementFromFixture, mock, Mock } from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { DropdownMenuButtonItemComponent, DropdownMenuComponent, LogoutIconComponent } from '@ods/system'; -import { AuthenticationService } from '@authentication'; -import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { MockComponent } from 'ng-mocks'; -import { UserProfileButtonContainerComponent } from './user-profile.button-container.component'; - -describe('UserProfileButtonContainerComponent', () => { - let component: UserProfileButtonContainerComponent; - let fixture: ComponentFixture<UserProfileButtonContainerComponent>; - - const authenticationService: Mock<AuthenticationService> = mock(AuthenticationService); - - const popupButtonContent: string = getDataTestIdOf('popup-button-content'); - const popupLogoutButton: string = getDataTestIdOf('popup-logout-button'); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [UserProfileButtonContainerComponent], - imports: [ - RouterTestingModule, - MockComponent(DropdownMenuComponent), - MockComponent(DropdownMenuButtonItemComponent), - MockComponent(LogoutIconComponent), - ], - providers: [ - { - provide: AuthenticationService, - useValue: authenticationService, - }, - ], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(UserProfileButtonContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('ngOnInit', () => { - it('should call authService to get current user initials', () => { - component.ngOnInit(); - - expect(authenticationService.getCurrentUserInitials).toHaveBeenCalled(); - }); - }); - - describe('popup button', () => { - it('should show initials', () => { - component.currentUserInitials = 'AV'; - fixture.detectChanges(); - - const popupButtonContentElement: HTMLElement = getElementFromFixture(fixture, popupButtonContent); - - expect(popupButtonContentElement.textContent.trim()).toEqual('AV'); - }); - }); - - describe('logout', () => { - it('should call authService logout', () => { - dispatchEventFromFixture(fixture, popupLogoutButton, 'itemClicked'); - - expect(authenticationService.logout).toHaveBeenCalled(); - }); - }); -}); diff --git a/alfa-client/apps/alfa-e2e/src/components/user-assistance/help-menu.component.e2e.ts b/alfa-client/apps/alfa-e2e/src/components/user-assistance/help-menu.component.e2e.ts index 6900b4f81bba5213341add0864bec162856037fb..9bdd8978dbaafbf31aec2301c0a66348be778a6d 100644 --- a/alfa-client/apps/alfa-e2e/src/components/user-assistance/help-menu.component.e2e.ts +++ b/alfa-client/apps/alfa-e2e/src/components/user-assistance/help-menu.component.e2e.ts @@ -1,4 +1,4 @@ -import { shouldHaveAttribute } from "../../support/cypress.util"; +import { shouldHaveAttribute } from '../../support/cypress.util'; /* * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den @@ -26,8 +26,8 @@ import { shouldHaveAttribute } from "../../support/cypress.util"; export class HelpMenuE2EComponent { private readonly root: string = 'help-menu'; private readonly button: string = 'help-menu-button'; - private readonly dropdownButton: string ='dropdown-button'; - private readonly openDocumentationButton: string = 'open-documentation-button'; + private readonly dropdownButton: string = 'dropdown-button'; + private readonly documentation: string = 'documentation'; private readonly openImpressumButton: string = 'impressum'; public getRoot() { @@ -42,15 +42,15 @@ export class HelpMenuE2EComponent { return this.getRoot().getTestElement(this.dropdownButton); } - public getOpenDocumentationButton() { - return this.getRoot().getTestElementWithOid(this.openDocumentationButton); + public getDocumentation() { + return this.getRoot().getTestElementWithOid(this.documentation); } public getImpressumButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.openImpressumButton) + return cy.getTestElement(this.openImpressumButton); } public impressumLinkIs(link: string): void { - shouldHaveAttribute(this.getImpressumButton().find('a'),'href', link) + shouldHaveAttribute(this.getImpressumButton().find('a'), 'href', link); } } diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/user-assistance/help-menu.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/user-assistance/help-menu.cy.ts index 4cc001a7f49f79190e8e1f53ef1242c36378d238..f2b5373adae2011b3bd3221041f783d5c8dd9bac 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/user-assistance/help-menu.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/user-assistance/help-menu.cy.ts @@ -25,7 +25,7 @@ import { HelpMenuE2EComponent } from 'apps/alfa-e2e/src/components/user-assistan import { HeaderE2EComponent } from 'apps/alfa-e2e/src/page-objects/header.po'; import { MainPage, waitForSpinnerToDisappear } from 'apps/alfa-e2e/src/page-objects/main.po'; import { dropCollections } from 'apps/alfa-e2e/src/support/cypress-helper'; -import { contains, exist, shouldHaveAttribute } from 'apps/alfa-e2e/src/support/cypress.util'; +import { exist } from 'apps/alfa-e2e/src/support/cypress.util'; import { loginAsSabine } from 'apps/alfa-e2e/src/support/user-util'; describe('Help Menu', () => { @@ -33,7 +33,7 @@ describe('Help Menu', () => { const header: HeaderE2EComponent = mainPage.getHeader(); const helpMenu: HelpMenuE2EComponent = header.getHelpMenu(); - const impressumLink: string = 'https://static.dev.sh.ozg-cloud.de/impressum' + const impressumLink: string = 'https://static.dev.sh.ozg-cloud.de/impressum'; before(() => { loginAsSabine(); @@ -50,7 +50,7 @@ describe('Help Menu', () => { it('should show "open documentation"', () => { helpMenu.getRoot().click(); - exist(helpMenu.getOpenDocumentationButton()); + exist(helpMenu.getDocumentation()); }); it('should show Impressum button and find link', () => { @@ -59,13 +59,7 @@ describe('Help Menu', () => { }); it('should open documentation', () => { - helpMenu - .getOpenDocumentationButton() - .find('a') - .invoke('removeAttr', 'target') - .click() - .url() - .should('include', 'benutzerleitfaden'); + helpMenu.getDocumentation().find('a').invoke('removeAttr', 'target').click().url().should('include', 'benutzerleitfaden'); }); }); }); diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts index ea3ca2d74ffe1eb2b6fb903a300d5b1b7b36e455..5a91a0813ffbe8dec5a89c9f526382168481bdcd 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts @@ -49,15 +49,16 @@ describe('KeycloakResourceService', () => { }); describe('getAll', () => { + const stateResource: StateResource<unknown> = createStateResource([]); + beforeEach(() => { - service.handleChanges = jest.fn(); + service.handleChanges = jest.fn().mockReturnValue(singleCold(stateResource)); }); - it('should return stateResource as observable', (done) => { - service.getAll().subscribe((stateResource) => { - expect(stateResource).toBe(service.stateResource.value); - done(); - }); + it('should return stateResource', () => { + const stateResource$: Observable<StateResource<unknown[]>> = service.getAll(); + + expect(stateResource$).toBeObservable(singleCold(stateResource)); }); it('should call handleChanges ', fakeAsync(() => { @@ -68,12 +69,25 @@ describe('KeycloakResourceService', () => { }); describe('handleChanges', () => { - it('should call doIfLoadingRequired', () => { - const doIfLoadingRequired: jest.SpyInstance<boolean> = jest.spyOn(ResourceUtil, 'doIfLoadingRequired'); + let doIfLoadingRequiredSpy: jest.SpyInstance<boolean>; + + beforeEach(() => { + doIfLoadingRequiredSpy = jest.spyOn(ResourceUtil, 'doIfLoadingRequired').mockImplementation(); + }); + it('should call doIfLoadingRequired', () => { service.handleChanges(emptyStateResource); - expect(doIfLoadingRequired).toHaveBeenCalled(); + expect(doIfLoadingRequiredSpy).toHaveBeenCalled(); + }); + + it('should return stateResource', (done) => { + service.stateResource.next(createStateResource([])); + + service.handleChanges(emptyStateResource).subscribe((stateResource: StateResource<[]>) => { + expect(stateResource).toEqual(createStateResource([])); + done(); + }); }); }); diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts index 572761923b93f3d3a21719659416b268fb17076f..ae74c73ee2c59c989a0db77dbaa1b881c4061448 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts @@ -22,22 +22,20 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { createEmptyStateResource, createStateResource, doIfLoadingRequired, StateResource } from '@alfa-client/tech-shared'; -import { BehaviorSubject, first, map, Observable, startWith, tap } from 'rxjs'; +import { BehaviorSubject, first, map, Observable, startWith, switchMap, tap } from 'rxjs'; export abstract class KeycloakResourceService<T> { - readonly stateResource: BehaviorSubject<StateResource<T[]>> = new BehaviorSubject({ - ...createStateResource<T[]>([]), - loaded: false, - }); + readonly stateResource: BehaviorSubject<StateResource<T[]>> = new BehaviorSubject(createEmptyStateResource()); public getAll(): Observable<StateResource<T[]>> { return this.stateResource .asObservable() - .pipe(tap((stateResource: StateResource<T[]>): void => this.handleChanges(stateResource))); + .pipe(switchMap((stateResource: StateResource<T[]>) => this.handleChanges(stateResource))); } - handleChanges(stateResource: StateResource<T[]>): void { + handleChanges(stateResource: StateResource<T[]>): Observable<StateResource<T[]>> { doIfLoadingRequired(stateResource, (): void => this.loadResource()); + return this.stateResource.asObservable(); } loadResource(): void { @@ -65,7 +63,7 @@ export abstract class KeycloakResourceService<T> { protected abstract _saveInKeycloak(item: T): Observable<T>; - public delete(id: string): Observable<StateResource<unknown>> { + public delete(id: string): Observable<StateResource<void>> { return this.handleLoading(this._deleteInKeycloak(id)); } diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.spec.ts index 43bc3d38565e870585b2ba846b8c2d681d977014..e86fbad2ab7cc0d9716dc13409990320dde904d7 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.spec.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.spec.ts @@ -1,6 +1,7 @@ import { AdminOrganisationsEinheit } from '@admin-client/organisations-einheit-shared'; import { mock, Mock } from '@alfa-client/test-utils'; import { TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker'; import KcAdminClient from '@keycloak/keycloak-admin-client'; import GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation'; import { createGroupRep } from '../../../organisations-einheit-shared/src/test/organisations-einheit'; @@ -85,4 +86,27 @@ describe('AdminOrganisationsEinheitRepository', () => { }); }); }); + + describe('delete', () => { + const organisationsEinheitId: string = faker.string.uuid(); + + beforeEach(() => { + kcAdminClient.groups = <any>{ + del: jest.fn().mockReturnValue(Promise.resolve()), + }; + }); + + it('should call kcAdminClient groups del', () => { + repository.delete(organisationsEinheitId); + + expect(kcAdminClient.groups['del']).toHaveBeenCalledWith({ id: organisationsEinheitId }); + }); + + it('should return void', (done) => { + repository.delete(organisationsEinheitId).subscribe((result: void) => { + expect(result).toBeUndefined(); + done(); + }); + }); + }); }); diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.ts index c2d454434e07562492f0c929bc710d7793a70403..abc3737d301f3f82b603a6bac0e5b85cd4e725ca 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.ts @@ -43,4 +43,8 @@ export class AdminOrganisationsEinheitRepository { attributes: group.attributes, }; } + + public delete(organisationseinheitId: string): Observable<void> { + return from(this.kcAdminClient.groups.del({ id: organisationseinheitId })); + } } diff --git a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.model.ts b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.model.ts index 3a93c42e6b0042f02dd9f509969444431ce6c374..5fac205bca0b5ebb2b5b025ad8450da89cfd8793 100644 --- a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.model.ts +++ b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.model.ts @@ -26,3 +26,8 @@ export interface AdminOrganisationsEinheit { name: string; attributes: { [key: string]: string[] }; } + +export interface OrganisationsEinheitDeleteDialogData { + organisationsEinheitName: string; + organisationsEinheitId: string; +} diff --git a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.spec.ts b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.spec.ts index db8b44f6aad3242e172a407fa6731ce992a60fcc..12b297f16e2cebbc8e774df56fe5d878fdcd6adb 100644 --- a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.spec.ts +++ b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.spec.ts @@ -23,8 +23,12 @@ */ import { AdminOrganisationsEinheit, AdminOrganisationsEinheitService } from '@admin-client/organisations-einheit-shared'; import { AdminOrganisationsEinheitRepository } from '@admin/keycloak-shared'; +import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; import { Mock, mock } from '@alfa-client/test-utils'; import { TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker'; +import { of } from 'rxjs'; +import { singleColdCompleted } from '../../../../tech-shared/test/marbles'; import { createAdminOrganisationsEinheit } from '../test/organisations-einheit'; describe('AdminOrganisationsEinheitService', () => { @@ -55,4 +59,23 @@ describe('AdminOrganisationsEinheitService', () => { expect(repository.create).toHaveBeenCalledWith(organisationsEinheit); }); }); + + describe('deleteInKeycloak', () => { + const organisationsEinheitId: string = faker.string.uuid(); + + it('should call repository delete', () => { + service._deleteInKeycloak(organisationsEinheitId); + + expect(repository.delete).toHaveBeenCalledWith(organisationsEinheitId); + }); + + it('should return result', () => { + const state: StateResource<unknown> = createEmptyStateResource(); + repository.delete.mockReturnValue(of(state)); + + const result = service._deleteInKeycloak(organisationsEinheitId); + + expect(result).toBeObservable(singleColdCompleted(state)); + }); + }); }); diff --git a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.ts b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.ts index 5c12d0613ef05cc74acb0ca4ee61794c61c5e95c..35f7580e9c46cd6262c7a68973d324e32022dcdc 100644 --- a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.ts +++ b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.ts @@ -45,6 +45,6 @@ export class AdminOrganisationsEinheitService extends KeycloakResourceService<Ad } _deleteInKeycloak(id: string): Observable<void> { - throw new Error('Method not implemented.'); + return this.repository.delete(id); } } diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.html b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..21de75aa26f03bde92ecc76d8a80a6e901565f7a --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.html @@ -0,0 +1,7 @@ +<admin-organisations-einheit-delete-dialog + [organisationsEinheitName]="organisationsEinheitName" + [deleteStateResource]="deleteStateResource$ | async" + (cancel)="closeDialog()" + (delete)="delete()" + data-test-id="organisations-einheit-delete-dialog" +/> diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.spec.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fad1b24d54d5e650b57182de2ce9f7751dddecb6 --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.spec.ts @@ -0,0 +1,145 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminOrganisationsEinheitService } from '@admin-client/organisations-einheit-shared'; +import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; +import { dispatchEventFromFixture, getMockComponent, Mock, mock } from '@alfa-client/test-utils'; +import { OzgcloudDialogService } from '@alfa-client/ui'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; +import { faker } from '@faker-js/faker'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { singleColdCompleted } from 'libs/tech-shared/test/marbles'; +import { Observable, of } from 'rxjs'; +import { OrganisationsEinheitDeleteDialogContainerComponent } from './organisations-einheit-delete-dialog-container.component'; +import { OrganisationsEinheitDeleteDialogComponent } from './organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component'; + +describe('OrganisationsEinheitDeleteDialogContainerComponent', () => { + let component: OrganisationsEinheitDeleteDialogContainerComponent; + let fixture: ComponentFixture<OrganisationsEinheitDeleteDialogContainerComponent>; + + let dialogService: Mock<OzgcloudDialogService>; + let organisationsEinheitService: Mock<AdminOrganisationsEinheitService>; + + const dialogData = { organisationsEinheitName: faker.word.sample(), organisationsEinheitId: faker.string.uuid() }; + + const organisationsEinheitDeleteDialog: string = getDataTestIdOf('organisations-einheit-delete-dialog'); + + beforeEach(() => { + dialogService = mock(OzgcloudDialogService); + organisationsEinheitService = { ...mock(AdminOrganisationsEinheitService), delete: jest.fn() }; + + TestBed.configureTestingModule({ + imports: [OrganisationsEinheitDeleteDialogContainerComponent], + providers: [ + { provide: OzgcloudDialogService, useValue: dialogService }, + { provide: AdminOrganisationsEinheitService, useValue: organisationsEinheitService }, + { + provide: DIALOG_DATA, + useValue: dialogData, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitDeleteDialogContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + describe('organisations einheit delete dialog', () => { + it('should exist', () => { + const stateResource: StateResource<void> = createEmptyStateResource(); + component.deleteStateResource$ = of(stateResource); + component.organisationsEinheitName = dialogData.organisationsEinheitName; + + const deleteDialogComponent: OrganisationsEinheitDeleteDialogComponent = getMockComponent( + fixture, + OrganisationsEinheitDeleteDialogComponent, + ); + + expect(deleteDialogComponent.organisationsEinheitName).toBe(component.organisationsEinheitName); + expect(deleteDialogComponent.deleteStateResource).toEqual(stateResource); + }); + + it('should call closeDialog on cancel emit', () => { + component.closeDialog = jest.fn(); + + dispatchEventFromFixture(fixture, organisationsEinheitDeleteDialog, 'cancel'); + + expect(component.closeDialog).toHaveBeenCalled(); + }); + + it('should call delete on delete emit', () => { + component.delete = jest.fn(); + + dispatchEventFromFixture(fixture, organisationsEinheitDeleteDialog, 'delete'); + + expect(component.delete).toHaveBeenCalled(); + }); + }); + }); + + describe('component', () => { + describe('constructor', () => { + it('should set organisationsEinheitName', () => { + expect(component.organisationsEinheitName).toBe(dialogData.organisationsEinheitName); + }); + + it('should set organisationsEinheitId', () => { + expect(component.organisationsEinheitId).toBe(dialogData.organisationsEinheitId); + }); + }); + + describe('closeDialog', () => { + it('should call dialogService closeAll', () => { + dialogService.closeAll = jest.fn(); + + component.closeDialog(); + + expect(dialogService.closeAll).toHaveBeenCalled(); + }); + }); + + describe('delete', () => { + const stateResource: StateResource<void> = createEmptyStateResource(); + const stateResource$: Observable<StateResource<void>> = of(stateResource); + const loadingStateResource: StateResource<void> = createEmptyStateResource(true); + const loadingStateResource$: Observable<StateResource<void>> = of(loadingStateResource); + + beforeEach(() => { + organisationsEinheitService.delete.mockReturnValue(stateResource$); + }); + + it('should call organisationsEinheitService delete', () => { + component.delete(); + + expect(organisationsEinheitService.delete).toHaveBeenCalledWith(dialogData.organisationsEinheitId); + }); + + it('should set deleteStateResource$', () => { + component.delete(); + + expect(component.deleteStateResource$).toBeObservable(singleColdCompleted(stateResource)); + }); + + it('should close dialog on delete done', () => { + component.delete(); + component.deleteStateResource$.subscribe(); + + expect(dialogService.closeAll).toHaveBeenCalled(); + }); + + it('should NOT close dialog on delete loading', () => { + organisationsEinheitService.delete.mockReturnValue(loadingStateResource$); + + component.delete(); + component.deleteStateResource$.subscribe(); + + expect(dialogService.closeAll).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..01ce6b8eac5000f08ba18da2a7d6480eccafb109 --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.ts @@ -0,0 +1,49 @@ +import { + AdminOrganisationsEinheitService, + OrganisationsEinheitDeleteDialogData, +} from '@admin-client/organisations-einheit-shared'; +import { createEmptyStateResource, isNotLoading, StateResource } from '@alfa-client/tech-shared'; +import { OzgcloudDialogService } from '@alfa-client/ui'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; +import { AsyncPipe } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { Observable, of, tap } from 'rxjs'; +import { OrganisationsEinheitDeleteDialogComponent } from './organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component'; + +@Component({ + selector: 'admin-organisations-einheit-delete-dialog-container', + standalone: true, + imports: [AsyncPipe, OrganisationsEinheitDeleteDialogComponent], + templateUrl: './organisations-einheit-delete-dialog-container.component.html', +}) +export class OrganisationsEinheitDeleteDialogContainerComponent { + private readonly dialogService = inject(OzgcloudDialogService); + private readonly dialogData: OrganisationsEinheitDeleteDialogData = inject(DIALOG_DATA); + private readonly organisationsEinheitService = inject(AdminOrganisationsEinheitService); + + public organisationsEinheitName: string; + public organisationsEinheitId: string; + + constructor() { + this.organisationsEinheitName = this.dialogData.organisationsEinheitName; + this.organisationsEinheitId = this.dialogData.organisationsEinheitId; + } + + public deleteStateResource$: Observable<StateResource<void>> = of(createEmptyStateResource<void>()); + + public closeDialog(): void { + this.dialogService.closeAll(); + } + + public delete(): void { + this.deleteStateResource$ = this.organisationsEinheitService + .delete(this.organisationsEinheitId) + .pipe(tap((stateResource: StateResource<void>) => this.closeDialogOnDeleteDone(stateResource))); + } + + private closeDialogOnDeleteDone(stateResource: StateResource<void>): void { + if (isNotLoading(stateResource)) { + this.dialogService.closeAll(); + } + } +} diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.html b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.html new file mode 100644 index 0000000000000000000000000000000000000000..1c75a946f561155e6bd95526ffeb58eb4b6cc4c1 --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.html @@ -0,0 +1,28 @@ +<div class="block flex max-w-xl flex-col gap-4 bg-background-100 p-8"> + <h1 class="text-xl">Organisationseinheit</h1> + + <h2 class="text-lg">{{ organisationsEinheitName }}</h2> + + <p> + <span class="font-bold">Achtung:</span> Durch das Entfernen der Organisationseinheit aus dieser Liste wird die + Organisationseinheit auch für alle Benutzer entfernt. + </p> + + <div class="flex justify-between"> + <ods-button + (clickEmitter)="cancel.emit()" + variant="outline" + text="Abbrechen" + dataTestId="dialog-cancel-button" + data-test-id="dialog-cancel-ods-button" + /> + + <ods-button-with-spinner + [stateResource]="deleteStateResource" + (clickEmitter)="delete.emit()" + text="Löschen" + dataTestId="dialog-delete-button" + data-test-id="dialog-delete-ods-button" + /> + </div> +</div> diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.spec.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5099b902cc490b643dd94764f282a66e9b45aead --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.spec.ts @@ -0,0 +1,79 @@ +import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; +import { + dispatchEventFromFixture, + existsAsHtmlElement, + getElementComponentFromFixtureByCss, + MockEvent, +} from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ButtonWithSpinnerComponent } from '@ods/component'; +import { ButtonComponent } from '@ods/system'; +import { MockComponent } from 'ng-mocks'; +import { getDataTestIdOf } from '../../../../../../../../tech-shared/test/data-test'; +import { OrganisationsEinheitDeleteDialogComponent } from './organisations-einheit-delete-dialog.component'; + +describe('OrganisationsEinheitDeleteDialogComponent', () => { + let component: OrganisationsEinheitDeleteDialogComponent; + let fixture: ComponentFixture<OrganisationsEinheitDeleteDialogComponent>; + + const deleteButton: string = getDataTestIdOf('dialog-delete-ods-button'); + const cancelButton: string = getDataTestIdOf('dialog-cancel-ods-button'); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + OrganisationsEinheitDeleteDialogComponent, + MockComponent(ButtonWithSpinnerComponent), + MockComponent(ButtonComponent), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitDeleteDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + describe('cancel button', () => { + it('should exist', () => { + existsAsHtmlElement(fixture, cancelButton); + }); + + it('should emit cancel on click', () => { + component.cancel.emit = jest.fn(); + + dispatchEventFromFixture(fixture, cancelButton, MockEvent.CLICK); + + expect(component.cancel.emit).toHaveBeenCalled(); + }); + }); + + describe('delete button', () => { + it('should exist', () => { + existsAsHtmlElement(fixture, deleteButton); + }); + + it('should have inputs', () => { + const stateResource: StateResource<void> = createEmptyStateResource(); + component.deleteStateResource = stateResource; + + fixture.detectChanges(); + + const button: ButtonWithSpinnerComponent = getElementComponentFromFixtureByCss(fixture, deleteButton); + expect(button.stateResource).toBe(stateResource); + }); + + it('should emit delete on click', () => { + component.delete.emit = jest.fn(); + + dispatchEventFromFixture(fixture, deleteButton, MockEvent.CLICK); + + expect(component.delete.emit).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..bf433fea87b9bd41af8e4e3e3133b46540b40f30 --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.ts @@ -0,0 +1,18 @@ +import { StateResource } from '@alfa-client/tech-shared'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ButtonWithSpinnerComponent } from '@ods/component'; +import { ButtonComponent } from '@ods/system'; + +@Component({ + selector: 'admin-organisations-einheit-delete-dialog', + standalone: true, + templateUrl: './organisations-einheit-delete-dialog.component.html', + imports: [ButtonWithSpinnerComponent, ButtonComponent], +}) +export class OrganisationsEinheitDeleteDialogComponent { + @Input() organisationsEinheitName: string; + @Input() deleteStateResource: StateResource<void>; + + @Output() cancel: EventEmitter<void> = new EventEmitter<void>(); + @Output() delete: EventEmitter<void> = new EventEmitter<void>(); +} diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.html b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.html index fc5ff5f37637c6c1cf2b9aa687ef25b0678b41f3..a64e70976ffe314813d4a5cea38c9915905dc7ca 100644 --- a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.html +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.html @@ -26,10 +26,22 @@ <ods-list data-test-id="organisations-einheit-list"> @for (organisationsEinheit of organisationsEinheitList; track $index) { <ods-list-item [attr.data-test-id]="(organisationsEinheit.name | convertForDataTest) + '-organisation-item'"> - <dl class="flex-1 basis-3/4 font-semibold"> - <dt class="sr-only">Name</dt> - <dd data-test-id="organisations-einheit-name">{{ organisationsEinheit.name }}</dd> - </dl> + <div class="space-between flex w-full items-center"> + <dl class="flex-1 basis-3/4 font-semibold"> + <dt class="sr-only">Name</dt> + <dd data-test-id="organisations-einheit-name">{{ organisationsEinheit.name }}</dd> + </dl> + <ods-open-dialog-button + [tooltip]="'Organisationseinheit löschen'" + variant="ghost" + size="fit" + dataTestClass="delete-button" + data-test-id="delete-organisations-einheit-dialog-button" + [dialogData]="{ organisationsEinheitName: organisationsEinheit.name, organisationsEinheitId: organisationsEinheit.id }" + > + <ods-delete-icon icon /> + </ods-open-dialog-button> + </div> </ods-list-item> } </ods-list> diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.spec.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.spec.ts index dd2fba6125d358ef26de554cdf79d2afdc40cf9b..c016c3c9c89a5e1767b684775068803afaf5280d 100644 --- a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.spec.ts +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.spec.ts @@ -23,10 +23,11 @@ */ import { AdminOrganisationsEinheit } from '@admin-client/organisations-einheit-shared'; import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; -import { existsAsHtmlElement, getElementFromFixture, mock } from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getElementFromFixture, getElementFromFixtureByType, mock } from '@alfa-client/test-utils'; import { CommonModule } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; +import { OpenDialogButtonComponent } from '@ods/component'; import { ExclamationIconComponent, ListComponent, ListItemComponent } from '@ods/system'; import { getConvertedDataTestIdOf, getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; @@ -39,6 +40,7 @@ describe('OrganisationsEinheitListComponent', () => { const listSelector: string = getDataTestIdOf('organisations-einheit-list'); const listItemSuffux: string = '-organisation-item'; + const deleteButtonTestId: string = getDataTestIdOf('delete-organisations-einheit-dialog-button'); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -48,7 +50,7 @@ describe('OrganisationsEinheitListComponent', () => { useValue: mock(ActivatedRoute), }, ], - imports: [CommonModule, ConvertForDataTestPipe], + imports: [CommonModule, ConvertForDataTestPipe, MockComponent(OpenDialogButtonComponent)], declarations: [ OrganisationsEinheitListComponent, MockComponent(ListComponent), @@ -92,5 +94,27 @@ describe('OrganisationsEinheitListComponent', () => { }); }); }); + + describe('open delete dialog button', () => { + const organisationsEinheit: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(); + + it('should exist', () => { + component.organisationsEinheitList = [organisationsEinheit]; + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, deleteButtonTestId); + }); + + it('should have inputs', () => { + component.organisationsEinheitList = [organisationsEinheit]; + + fixture.detectChanges(); + const dialog: OpenDialogButtonComponent = getElementFromFixtureByType(fixture, OpenDialogButtonComponent); + + expect(dialog.dialogData.organisationsEinheitName).toBe(organisationsEinheit.name); + expect(dialog.dialogData.organisationsEinheitId).toBe(organisationsEinheit.id); + }); + }); }); }); diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.ts index 7efc0bbbe6727d1d558c63bad8ffe6a2d08b9d35..9fabaed44ebd2111a45a4812193ca6fa774202c9 100644 --- a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.ts +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.ts @@ -22,23 +22,27 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { AdminOrganisationsEinheit } from '@admin-client/organisations-einheit-shared'; -import { ConvertForDataTestPipe, ToResourceUriPipe } from '@alfa-client/tech-shared'; +import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; +import { DIALOG_COMPONENT } from '@alfa-client/ui'; import { CommonModule } from '@angular/common'; import { Component, Input } from '@angular/core'; -import { ExclamationIconComponent, ListComponent, ListItemComponent, TooltipDirective } from '@ods/system'; +import { OpenDialogButtonComponent } from '@ods/component'; +import { DeleteIconComponent, ListComponent, ListItemComponent, TooltipDirective } from '@ods/system'; +import { OrganisationsEinheitDeleteDialogContainerComponent } from './organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component'; @Component({ selector: 'admin-organisations-einheit-list', templateUrl: './organisations-einheit-list.component.html', standalone: true, + providers: [{ provide: DIALOG_COMPONENT, useValue: OrganisationsEinheitDeleteDialogContainerComponent }], imports: [ CommonModule, ListComponent, ListItemComponent, - ExclamationIconComponent, - TooltipDirective, - ToResourceUriPipe, ConvertForDataTestPipe, + OpenDialogButtonComponent, + TooltipDirective, + DeleteIconComponent, ], }) export class OrganisationsEinheitListComponent { diff --git a/alfa-client/libs/admin/user-profile/.eslintrc.json b/alfa-client/libs/admin/user-profile/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..b10f9813a8f5c59432cf245301dc4d01a8031fd1 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "lib", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "lib", + "style": "kebab-case" + } + ] + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/alfa-client/libs/admin/user-profile/README.md b/alfa-client/libs/admin/user-profile/README.md new file mode 100644 index 0000000000000000000000000000000000000000..274c5f872b16cc6d084af4624ac985d6def1ab1d --- /dev/null +++ b/alfa-client/libs/admin/user-profile/README.md @@ -0,0 +1,7 @@ +# admin-user-profile + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test admin-user-profile` to execute the unit tests. diff --git a/alfa-client/libs/admin/user-profile/jest.config.ts b/alfa-client/libs/admin/user-profile/jest.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..8670098e4b2ced9d4dc48f74066897f0cde39ff4 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/jest.config.ts @@ -0,0 +1,21 @@ +export default { + displayName: 'admin-user-profile', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'], + coverageDirectory: '../../../coverage/libs/admin/user-profile', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '<rootDir>/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/alfa-client/libs/admin/user-profile/project.json b/alfa-client/libs/admin/user-profile/project.json new file mode 100644 index 0000000000000000000000000000000000000000..fc4e68c48443f646a90034421e184a837300707c --- /dev/null +++ b/alfa-client/libs/admin/user-profile/project.json @@ -0,0 +1,22 @@ +{ + "name": "admin-user-profile", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/admin/user-profile/src", + "prefix": "lib", + "projectType": "library", + "tags": [], + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": [ + "{workspaceRoot}/coverage/{projectRoot}" + ], + "options": { + "jestConfig": "libs/admin/user-profile/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/alfa-client/libs/admin/user-profile/src/index.ts b/alfa-client/libs/admin/user-profile/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.html b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.html new file mode 100644 index 0000000000000000000000000000000000000000..c3fe2d2837418f91d393a6de8cb98b20b271a0a5 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.html @@ -0,0 +1,3 @@ +<ods-dropdown-menu-link-item caption="Leitfaden für die Administration" text="PDF öffnen" [url]="url"> + <ods-file-icon icon fileType="pdf" size="medium" /> +</ods-dropdown-menu-link-item> diff --git a/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.scss b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..bcbd245ce7ef1ee60e79e99fce4f19f468a8c145 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.scss @@ -0,0 +1,3 @@ +:host { + white-space: nowrap; +} diff --git a/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.spec.ts b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..174d7d1fddc546ec91902bd0ee6d7a2f4509f561 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DropdownMenuLinkItemComponent, FileIconComponent } from '@ods/system'; +import { MockComponent } from 'ng-mocks'; +import { DocumentationComponent } from './documentation.component'; + +describe('DocumentationComponent', () => { + let component: DocumentationComponent; + let fixture: ComponentFixture<DocumentationComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DocumentationComponent, MockComponent(DropdownMenuLinkItemComponent), MockComponent(FileIconComponent)], + }).compileComponents(); + + fixture = TestBed.createComponent(DocumentationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.ts b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3efae263c5f161d036cecce326dd636e292ce25 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import { DropdownMenuLinkItemComponent, FileIconComponent } from '@ods/system'; + +@Component({ + selector: 'admin-documentation', + templateUrl: './documentation.component.html', + styleUrls: ['./documentation.component.scss'], + standalone: true, + imports: [DropdownMenuLinkItemComponent, FileIconComponent], +}) +export class DocumentationComponent { + @Input() url: string; +} diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.html b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9c5b0aa958eaadea5a44ecb92f5cbe45c05b06e6 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.html @@ -0,0 +1,3 @@ +<ods-dropdown-menu-button-item caption="Abmelden" (clickEmitter)="logout.emit()" data-test-id="popup-logout-button"> + <ods-logout-icon icon class="fill-primary" /> +</ods-dropdown-menu-button-item> diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.spec.ts b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..092b7217e8fe78d548dad54ed264c61c4cb0fdcd --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.spec.ts @@ -0,0 +1,42 @@ +import { dispatchEventFromFixture, MockEvent } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { expect } from '@jest/globals'; +import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test'; +import { AdminUserLogoutButtonComponent } from './admin-user-logout-button.component'; + +describe('AdminUserLogoutButtonComponent', () => { + let component: AdminUserLogoutButtonComponent; + let fixture: ComponentFixture<AdminUserLogoutButtonComponent>; + + const logoutButtonTestId: string = getDataTestIdOf('popup-logout-button'); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AdminUserLogoutButtonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AdminUserLogoutButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + describe('menu button item', () => { + describe('output', () => { + describe('clickEmitter', () => { + it('should emit', () => { + component.logout.emit = jest.fn(); + + dispatchEventFromFixture(fixture, logoutButtonTestId, MockEvent.CLICK); + + expect(component.logout.emit).toHaveBeenCalled(); + }); + }); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.ts b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e3f4cd7f873a43d3012767e30b5f58738cbee69 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.ts @@ -0,0 +1,12 @@ +import { Component, EventEmitter, Output } from '@angular/core'; +import { DropdownMenuButtonItemComponent, LogoutIconComponent } from '@ods/system'; + +@Component({ + selector: 'admin-user-logout-button', + standalone: true, + templateUrl: './admin-user-logout-button.component.html', + imports: [DropdownMenuButtonItemComponent, LogoutIconComponent], +}) +export class AdminUserLogoutButtonComponent { + @Output() logout = new EventEmitter<void>(); +} diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.html b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.html new file mode 100644 index 0000000000000000000000000000000000000000..78c636770990371bb05b7e6eef2bcca474ca67f0 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.html @@ -0,0 +1,8 @@ +<div + role="img" + class="flex size-9 items-center justify-center rounded-full border-2 border-transparent bg-ozggray-900 hover:border-primary" +> + <p class="font-semibold text-whitetext" data-test-id="popup-button-content"> + {{ currentUserInitials }} + </p> +</div> diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.spec.ts b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d14d9482666d406fa59e7d97856497e2f3ba930b --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AdminUserMenuButtonComponent } from './admin-user-menu-button.component'; + +describe('AdminUserMenuButtonComponent', () => { + let component: AdminUserMenuButtonComponent; + let fixture: ComponentFixture<AdminUserMenuButtonComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AdminUserMenuButtonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AdminUserMenuButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.ts b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..02e3f12cef1b770fd4ee46a561d5949a84bc3e92 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.ts @@ -0,0 +1,10 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'admin-user-menu-button', + standalone: true, + templateUrl: './admin-user-menu-button.component.html', +}) +export class AdminUserMenuButtonComponent { + @Input() currentUserInitials: string; +} diff --git a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.html b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.html similarity index 66% rename from alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.html rename to alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.html index 29fc9153463819b76fb30d8f2deef1dd77e7a9c4..7f5574fb4b4a3e18aa19c5a44094644825a59433 100644 --- a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.html +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.html @@ -24,20 +24,15 @@ --> <ods-dropdown-menu buttonClass="rounded-full"> - <div - button-content - role="img" - class="flex size-9 items-center justify-center rounded-full border-2 border-transparent bg-ozggray-900 hover:border-primary" - > - <p class="font-semibold text-whitetext" data-test-id="popup-button-content"> - {{ currentUserInitials }} - </p> - </div> - <ods-dropdown-menu-button-item - caption="Abmelden" - (itemClicked)="authenticationService.logout()" - data-test-id="popup-logout-button" - > - <ods-logout-icon icon /> - </ods-dropdown-menu-button-item> + <admin-user-menu-button button-content [currentUserInitials]="currentUserInitials"></admin-user-menu-button> + + <admin-user-logout-button (logout)="authenticationService.logout()"></admin-user-logout-button> + + @if (apiRootStateResource.resource | hasLink: ApiRootLinkRel.DOCUMENTATIONS) { + <div class="h-2"></div> + <admin-documentation + [url]="apiRootStateResource.resource | getUrl: ApiRootLinkRel.DOCUMENTATIONS" + data-test-id="admin-documentation" + /> + } </ods-dropdown-menu> diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.spec.ts b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb23876d9563a75acb6816a5cc5cca5285b2480a --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.spec.ts @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import { ApiRootLinkRel, ApiRootResource } from '@alfa-client/api-root-shared'; +import { createStateResource, StateResource } from '@alfa-client/tech-shared'; +import { dispatchEventFromFixtureByType, existsAsHtmlElement, getElementComponentFromFixtureByCss, getElementFromFixture, mock, Mock, notExistsAsHtmlElement, } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AuthenticationService } from '@authentication'; +import { expect } from '@jest/globals'; +import { getUrl } from '@ngxp/rest'; +import { DropdownMenuButtonItemComponent, DropdownMenuComponent, LogoutIconComponent } from '@ods/system'; +import { MockComponent } from 'ng-mocks'; +import { createApiRootResource } from '../../../../../api-root-shared/test/api-root'; +import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test'; +import { DocumentationComponent } from '../documentation/documentation.component'; +import { AdminUserLogoutButtonComponent } from '../user-logout-button/admin-user-logout-button.component'; +import { UserProfileButtonContainerComponent } from './user-profile.button-container.component'; + +describe('UserProfileButtonContainerComponent', () => { + let component: UserProfileButtonContainerComponent; + let fixture: ComponentFixture<UserProfileButtonContainerComponent>; + + let authenticationService: Mock<AuthenticationService>; + + const popupButtonContent: string = getDataTestIdOf('popup-button-content'); + const popupLogoutButton: string = getDataTestIdOf('popup-logout-button'); + const documentationTestId: string = getDataTestIdOf('admin-documentation'); + + const apiRootStateResource: StateResource<ApiRootResource> = createStateResource(createApiRootResource()); + + beforeEach(() => { + authenticationService = mock(AuthenticationService); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [UserProfileButtonContainerComponent], + imports: [ + RouterTestingModule, + MockComponent(DropdownMenuComponent), + MockComponent(DropdownMenuButtonItemComponent), + MockComponent(LogoutIconComponent), + ], + providers: [ + { + provide: AuthenticationService, + useValue: authenticationService, + }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserProfileButtonContainerComponent); + component = fixture.componentInstance; + component.apiRootStateResource = apiRootStateResource; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('ngOnInit', () => { + it('should call authService to get current user initials', () => { + component.ngOnInit(); + + expect(authenticationService.getCurrentUserInitials).toHaveBeenCalled(); + }); + }); + + describe('template', () => { + describe('popup button', () => { + it('should show initials', () => { + component.currentUserInitials = 'AV'; + fixture.detectChanges(); + + const popupButtonContentElement: HTMLElement = getElementFromFixture(fixture, popupButtonContent); + + expect(popupButtonContentElement.textContent.trim()).toEqual('AV'); + }); + }); + + describe('logout', () => { + it('should call authService logout', () => { + dispatchEventFromFixtureByType(fixture, AdminUserLogoutButtonComponent, 'logout'); + + expect(authenticationService.logout).toHaveBeenCalled(); + }); + }); + + describe('documentation', () => { + it('should exists', () => { + component.apiRootStateResource = createStateResource(createApiRootResource([ApiRootLinkRel.DOCUMENTATIONS])); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, documentationTestId); + }); + + it('should NOT exists', () => { + component.apiRootStateResource = createStateResource(createApiRootResource([])); + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, documentationTestId); + }); + + it('should have inputs', () => { + component.apiRootStateResource = createStateResource(createApiRootResource([ApiRootLinkRel.DOCUMENTATIONS])); + + fixture.detectChanges(); + const documentationComponent: DocumentationComponent = getElementComponentFromFixtureByCss(fixture, documentationTestId); + + expect(documentationComponent.url).toEqual( + getUrl(component.apiRootStateResource.resource, ApiRootLinkRel.DOCUMENTATIONS), + ); + }); + }); + }); +}); diff --git a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile.button-container.component.ts b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component.ts similarity index 59% rename from alfa-client/apps/admin/src/common/user-profile-button-container/user-profile.button-container.component.ts rename to alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component.ts index f2f4bd8351b8eb6be525ce81a68c8aec3c081a6e..6a7d7299beab272fc36cd93602f449dd0bff1af7 100644 --- a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile.button-container.component.ts +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component.ts @@ -21,20 +21,36 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, OnInit } from '@angular/core'; -import { DropdownMenuButtonItemComponent, DropdownMenuComponent, LogoutIconComponent } from '@ods/system'; +import { ApiRootLinkRel, ApiRootResource } from '@alfa-client/api-root-shared'; +import { GetUrlPipe, HasLinkPipe, StateResource } from '@alfa-client/tech-shared'; +import { Component, inject, Input, OnInit } from '@angular/core'; import { AuthenticationService } from '@authentication'; +import { DropdownMenuComponent } from '@ods/system'; +import { DocumentationComponent } from '../documentation/documentation.component'; +import { AdminUserLogoutButtonComponent } from '../user-logout-button/admin-user-logout-button.component'; +import { AdminUserMenuButtonComponent } from '../user-menu-button/admin-user-menu-button.component'; @Component({ selector: 'user-profile-button-container', templateUrl: './user-profile-button-container.component.html', standalone: true, - imports: [DropdownMenuComponent, DropdownMenuButtonItemComponent, LogoutIconComponent], + imports: [ + DropdownMenuComponent, + HasLinkPipe, + GetUrlPipe, + DocumentationComponent, + AdminUserLogoutButtonComponent, + AdminUserMenuButtonComponent, + ], }) export class UserProfileButtonContainerComponent implements OnInit { + @Input() apiRootStateResource: StateResource<ApiRootResource>; + public currentUserInitials: string; - constructor(public authenticationService: AuthenticationService) {} + public readonly authenticationService: AuthenticationService = inject(AuthenticationService); + + public readonly ApiRootLinkRel = ApiRootLinkRel; ngOnInit(): void { this.currentUserInitials = this.authenticationService.getCurrentUserInitials(); diff --git a/alfa-client/libs/admin/user-profile/src/test-setup.ts b/alfa-client/libs/admin/user-profile/src/test-setup.ts new file mode 100644 index 0000000000000000000000000000000000000000..c408668266d2fec3a9803c0ec044bc163fb987fe --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/test-setup.ts @@ -0,0 +1,12 @@ +import '@testing-library/jest-dom'; +import 'jest-preset-angular/setup-jest'; + +import { getTestBed } from '@angular/core/testing'; +import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; + +getTestBed().resetTestEnvironment(); +getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { + teardown: { destroyAfterEach: false }, + errorOnUnknownProperties: true, + errorOnUnknownElements: true, +}); diff --git a/alfa-client/libs/admin/user-profile/tsconfig.json b/alfa-client/libs/admin/user-profile/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..8ca9ad312c2bd4dc364383853ddd91a2ed8f86fd --- /dev/null +++ b/alfa-client/libs/admin/user-profile/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "target": "es2022" + } +} diff --git a/alfa-client/libs/admin/user-profile/tsconfig.lib.json b/alfa-client/libs/admin/user-profile/tsconfig.lib.json new file mode 100644 index 0000000000000000000000000000000000000000..8441346f6e5858b2ef4235cb3c3160eda256f94a --- /dev/null +++ b/alfa-client/libs/admin/user-profile/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/alfa-client/libs/admin/user-profile/tsconfig.spec.json b/alfa-client/libs/admin/user-profile/tsconfig.spec.json new file mode 100644 index 0000000000000000000000000000000000000000..e637bf83b59243f9ebc9b37842cfbf3f159bba47 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/tsconfig.spec.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts index 3be33cbcdfd5b941af8ce9ef4a51c63160419bb5..724564f208c9f26cb6265da1bbad81b52ad92b13 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts @@ -26,8 +26,13 @@ import { ROUTES } from '@admin-client/shared'; import { User, UserService } from '@admin-client/user-shared'; import { PatchConfig } from '@admin/keycloak-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { createEmptyStateResource, createStateResource, StateResource } from '@alfa-client/tech-shared'; -import { Mock, mock } from '@alfa-client/test-utils'; +import { + createEmptyStateResource, + createLoadingStateResource, + createStateResource, + StateResource, +} from '@alfa-client/tech-shared'; +import { Mock, mock, mockWindowError } from '@alfa-client/test-utils'; import { SnackBarService } from '@alfa-client/ui'; import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { AbstractControl, FormControl, FormGroup, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; @@ -159,7 +164,13 @@ describe('UserFormService', () => { }); describe('initOrganisationsEinheiten', () => { + beforeEach(() => { + formService._addOrganisationsEinheitenToForm = jest.fn(); + }); + it('should call adminOrganisationsEinheitService getAll', () => { + formService._initOrganisationsEinheiten(); + expect(adminOrganisationsEinheitService.getAll).toHaveBeenCalled(); }); @@ -170,8 +181,6 @@ describe('UserFormService', () => { }); it('should call addOrganisationsEinheitenToForm ', fakeAsync(() => { - formService._addOrganisationsEinheitenToForm = jest.fn(); - formService._initOrganisationsEinheiten().subscribe(); tick(); @@ -189,6 +198,15 @@ describe('UserFormService', () => { it('should set initOrganisationsEinheiten$', () => { expect(formService['_initOrganisationsEinheiten$']).toBeDefined(); }); + + it('should not throw any exception on loading state resource', () => { + adminOrganisationsEinheitService.getAll.mockReturnValue(of(createLoadingStateResource())); + const errorMock: any = mockWindowError(); + + formService._initOrganisationsEinheiten(); + + expect(errorMock).not.toHaveBeenCalled(); + }); }); describe('addOrganisationsEinheitenToForm', () => { diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts index 7cf9887b872a3f608e8cb8eb0c8b064b12b7fba5..494673b77cf049e6219418f7a726173af20e3fd0 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts @@ -26,7 +26,7 @@ import { ROUTES } from '@admin-client/shared'; import { User, UserService } from '@admin-client/user-shared'; import { KeycloakFormService, PatchConfig } from '@admin/keycloak-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { createEmptyStateResource, EMPTY_STRING, mapToResource, StateResource } from '@alfa-client/tech-shared'; +import { createEmptyStateResource, EMPTY_STRING, isLoaded, mapToResource, StateResource } from '@alfa-client/tech-shared'; import { SnackBarService } from '@alfa-client/ui'; import { Injectable, OnDestroy } from '@angular/core'; import { @@ -39,7 +39,7 @@ import { Validators, } from '@angular/forms'; import { UrlSegment } from '@angular/router'; -import { catchError, Observable, of, Subscription, tap } from 'rxjs'; +import { catchError, filter, Observable, of, Subscription, tap } from 'rxjs'; @Injectable() export class UserFormService extends KeycloakFormService<User> implements OnDestroy { @@ -139,6 +139,7 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest _initOrganisationsEinheiten(): Observable<AdminOrganisationsEinheit[]> { const organisationsEinheitenGroup: UntypedFormGroup = this.getOrganisationsEinheitenGroup(); return this.adminOrganisationsEinheitService.getAll().pipe( + filter(isLoaded), mapToResource<AdminOrganisationsEinheit[]>(), tap((organisationsEinheiten: AdminOrganisationsEinheit[]): void => { this.setOrganisationsEinheitenIdsInMap(organisationsEinheiten); diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.spec.ts index e3b7bcedafc499a5bf7187d096e9bcb1eb9d6105..e03fe86039ad5c895d6be78eb173a56e78aa445e 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.spec.ts @@ -20,6 +20,7 @@ describe('UserListContainerComponent', () => { userService = { ...mock(UserService), getAll: jest.fn().mockReturnValue(usersStateResource$), + refresh: jest.fn(), }; await TestBed.configureTestingModule({ @@ -55,4 +56,12 @@ describe('UserListContainerComponent', () => { expect(userList.usersStateResource).toBe(usersStateResource); }); }); + + describe('on destroy', () => { + it('should call service to refresh list', () => { + component.ngOnDestroy(); + + expect(userService.refresh).toHaveBeenCalled(); + }); + }); }); diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.ts index 371df8b2664808b39e524439d4ae45d63016f177..ac2e37357acff04c7849031dec3d2cefe30f6a44 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.ts @@ -1,8 +1,8 @@ import { User, UserService } from '@admin-client/user-shared'; -import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; +import { StateResource } from '@alfa-client/tech-shared'; import { AsyncPipe } from '@angular/common'; -import { Component, inject, OnInit } from '@angular/core'; -import { Observable, of } from 'rxjs'; +import { Component, inject, OnDestroy, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; import { UserListComponent } from './user-list/user-list.component'; @Component({ @@ -11,12 +11,16 @@ import { UserListComponent } from './user-list/user-list.component'; imports: [UserListComponent, AsyncPipe], templateUrl: './user-list-container.component.html', }) -export class UserListContainerComponent implements OnInit { +export class UserListContainerComponent implements OnInit, OnDestroy { private userService = inject(UserService); - public usersStateResource$: Observable<StateResource<User[]>> = of(createEmptyStateResource<User[]>()); + public usersStateResource$: Observable<StateResource<User[]>>; ngOnInit(): void { this.usersStateResource$ = this.userService.getAll(); } + + ngOnDestroy(): void { + this.userService.refresh(); + } } diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.html b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.html index c131614ac1e604fb3afc63765f97d57ad73e9c17..e2db54f1168e688391e37bd141c058e60dd45022 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.html @@ -25,8 +25,10 @@ --> <h1 class="heading-1 mb-4" data-test-id="user-list-headline">Benutzer & Rollen</h1> <ods-routing-button [linkPath]="ROUTES.BENUTZER_NEU" text="Benutzer hinzufügen" class="mb-4 w-fit" dataTestId="add-user-button" /> -<ods-list> - @for (user of usersStateResource.resource; track $index) { - <admin-user [user]="user" class="block w-full" /> - } -</ods-list> +<ods-spinner [stateResource]="usersStateResource"> + <ods-list data-test-id="user-list"> + @for (user of usersStateResource.resource; track $index) { + <admin-user [user]="user" class="block w-full" /> + } + </ods-list> +</ods-spinner> diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.spec.ts index 23bc4e3efe65add8ea04eaff6d4324b907190dc8..fe161b5c1fc09a5a9b2def8a5a500b261358a9fe 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.spec.ts @@ -26,7 +26,7 @@ import { User } from '@admin-client/user-shared'; import { createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared'; import { getMockComponent } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RoutingButtonComponent } from '@ods/component'; +import { RoutingButtonComponent, SpinnerComponent } from '@ods/component'; import { MockComponent } from 'ng-mocks'; import { createUser } from '../../../../../user-shared/test/user'; import { UserListComponent } from './user-list.component'; @@ -38,8 +38,10 @@ describe('UsersListComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [UserListComponent], - declarations: [MockComponent(RoutingButtonComponent), MockComponent(UserComponent)], + imports: [ + UserListComponent, + [MockComponent(RoutingButtonComponent), MockComponent(UserComponent), MockComponent(SpinnerComponent)], + ], }).compileComponents(); fixture = TestBed.createComponent(UserListComponent); diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.ts index 984d6540945e2739b3b89e66eb9fc03ebc7264d6..61df6b59bfa24732237a7837e9a0d67e7f2f90cc 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.ts @@ -26,7 +26,7 @@ import { User } from '@admin-client/user-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { AsyncPipe } from '@angular/common'; import { Component, Input } from '@angular/core'; -import { ButtonWithSpinnerComponent, RoutingButtonComponent } from '@ods/component'; +import { ButtonWithSpinnerComponent, RoutingButtonComponent, SpinnerComponent } from '@ods/component'; import { ListComponent, ListItemComponent } from '@ods/system'; import { UserComponent } from './user/user.component'; @@ -34,7 +34,15 @@ import { UserComponent } from './user/user.component'; selector: 'admin-user-list', templateUrl: './user-list.component.html', standalone: true, - imports: [ButtonWithSpinnerComponent, ListComponent, UserComponent, AsyncPipe, ListItemComponent, RoutingButtonComponent], + imports: [ + ButtonWithSpinnerComponent, + ListComponent, + UserComponent, + AsyncPipe, + ListItemComponent, + RoutingButtonComponent, + SpinnerComponent, + ], }) export class UserListComponent { @Input() usersStateResource: StateResource<User[]>; diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.html index edd8aff832236fb11a9b372a47c40f4bbb8d06d3..007a02ec854d9001c4eaef88f688760d6db3fa8d 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.html @@ -23,7 +23,7 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<div class="mt-4 flex flex-col gap-4"> +<div class="mt-4 flex max-w-72 flex-col gap-4"> @if (bescheidResource | hasLink: BescheidLinkRel.CREATE_DOCUMENT) { <alfa-bescheid-wizard-create-document-button-container [bescheidResource]="bescheidResource" diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.html b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.html index cfa93fbc6b3b9dbd01834a8d31c1bf74f4088299..de2d67af94b520ec15bb8292ef9278cb78d8e395 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.html +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.html @@ -23,9 +23,7 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<ods-attachment-wrapper> - <alfa-binary-file-list - [binaryFileListStateResource]="binaryFileListStateResource$ | async" - [listOrientation]="listOrientation" - ></alfa-binary-file-list> -</ods-attachment-wrapper> +<alfa-binary-file-list + [binaryFileListStateResource]="binaryFileListStateResource$ | async" + [listOrientation]="listOrientation" +></alfa-binary-file-list> diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.spec.ts b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.spec.ts index e9473e9ee486bc437328ee6fe85227d12c6293e5..6cca0983e8268484b83772fa1f78dafda782e70e 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.spec.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.spec.ts @@ -34,27 +34,19 @@ import { of } from 'rxjs'; import { BinaryFileListContainerComponent } from './binary-file-list-container.component'; import { BinaryFileListComponent } from './binary-file-list/binary-file-list.component'; -import { AttachmentWrapperComponent } from '@ods/system'; - describe('BinaryFileListContainerComponent', () => { let component: BinaryFileListContainerComponent; let fixture: ComponentFixture<BinaryFileListContainerComponent>; const binaryFileService: Mock<BinaryFileService> = mock(BinaryFileService); - const binaryFileStateResource: StateResource<BinaryFileResource> = createStateResource( - createBinaryFileResource(), - ); + const binaryFileStateResource: StateResource<BinaryFileResource> = createStateResource(createBinaryFileResource()); const resource: Resource = createDummyResource(); const linkRel: LinkRelationName = DummyLinkRel.DUMMY; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - BinaryFileListContainerComponent, - MockComponent(BinaryFileListComponent), - MockComponent(AttachmentWrapperComponent), - ], + declarations: [BinaryFileListContainerComponent, MockComponent(BinaryFileListComponent)], providers: [ { provide: BinaryFileService, @@ -85,8 +77,10 @@ describe('BinaryFileListContainerComponent', () => { describe('binary file list', () => { it('should be called with binary file state resource', () => { - const binaryFileListComponent: BinaryFileListComponent = - getMockComponent<BinaryFileListComponent>(fixture, BinaryFileListComponent); + const binaryFileListComponent: BinaryFileListComponent = getMockComponent<BinaryFileListComponent>( + fixture, + BinaryFileListComponent, + ); expect(binaryFileListComponent.binaryFileListStateResource).toBe(binaryFileStateResource); }); diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.html b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.html index 70054de869940144f9b0e1d3fa0eb7c23ea72a33..a4ee6adc69c75525b8a32dd2e0ad5a9352701c2d 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.html +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.html @@ -23,11 +23,13 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<div [binaryFileListOrientation]="listOrientation"> - <alfa-binary-file2-container - *ngFor="let binaryFile of binaryFileListStateResource.resource | toEmbeddedResources: binaryFileListLinkRel.FILE_LIST" - [file]="binaryFile" - [deletable]="false" - > - </alfa-binary-file2-container> -</div> +@if (binaryFileListStateResource.resource | toEmbeddedResources: binaryFileListLinkRel.FILE_LIST; as binaryFileList) { + @if (binaryFileList.length) { + <ods-attachment-wrapper data-test-id="binary-file-list-wrapper"> + <div [binaryFileListOrientation]="listOrientation"> + <alfa-binary-file2-container *ngFor="let binaryFile of binaryFileList" [file]="binaryFile" [deletable]="false"> + </alfa-binary-file2-container> + </div> + </ods-attachment-wrapper> + } +} diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.spec.ts b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.spec.ts index fbbc820600ee4d1b3a9235dafd3515f9c1f16404..354a9d2be51cb3f1bd9bdb20d1525ed75b653904 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.spec.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.spec.ts @@ -23,9 +23,11 @@ */ import { BinaryFileListResource, BinaryFileResource } from '@alfa-client/binary-file-shared'; import { createStateResource, StateResource, ToEmbeddedResourcesPipe } from '@alfa-client/tech-shared'; -import { getMockComponent } from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getMockComponent, notExistsAsHtmlElement } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AttachmentWrapperComponent } from '@ods/system'; import { createBinaryFileListResource, createBinaryFileResource } from 'libs/binary-file-shared/test/binary-file'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent, MockDirective } from 'ng-mocks'; import { BinaryFile2ContainerComponent } from '../../binary-file2-container/binary-file2-container.component'; import { BinaryFileListOrientationDirective } from '../../directive/binary-file-list-orientation/binary-file-list-orientation.directive'; @@ -36,9 +38,11 @@ describe('BinaryFileListComponent', () => { let fixture: ComponentFixture<BinaryFileListComponent>; const binaryFile: BinaryFileResource = createBinaryFileResource(); - const binaryFileListStateResource: StateResource<BinaryFileListResource> = createStateResource( - createBinaryFileListResource([binaryFile]), - ); + function getBinaryFileListStateResource(binaryFiles: BinaryFileResource[]): StateResource<BinaryFileListResource> { + return createStateResource(createBinaryFileListResource(binaryFiles)); + } + + const wrapperSelector: string = getDataTestIdOf('binary-file-list-wrapper'); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -46,13 +50,14 @@ describe('BinaryFileListComponent', () => { BinaryFileListComponent, ToEmbeddedResourcesPipe, MockComponent(BinaryFile2ContainerComponent), + MockComponent(AttachmentWrapperComponent), MockDirective(BinaryFileListOrientationDirective), ], }).compileComponents(); fixture = TestBed.createComponent(BinaryFileListComponent); component = fixture.componentInstance; - component.binaryFileListStateResource = binaryFileListStateResource; + component.binaryFileListStateResource = getBinaryFileListStateResource([binaryFile]); fixture.detectChanges(); }); @@ -60,6 +65,22 @@ describe('BinaryFileListComponent', () => { expect(component).toBeTruthy(); }); + describe('template', () => { + describe('attachment wrapper', () => { + it('should show', () => { + existsAsHtmlElement(fixture, wrapperSelector); + }); + + it('should hide', () => { + component.binaryFileListStateResource = getBinaryFileListStateResource([]); + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, wrapperSelector); + }); + }); + }); + describe('binary file container', () => { it('should be called with file', () => { const binaryFileContainerComponent: BinaryFile2ContainerComponent = getMockComponent<BinaryFile2ContainerComponent>( diff --git a/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.ts b/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.ts index 3a1a42958aca047046c5aa29edaed15dc77a1705..956be5722cdcd4966e12c8a5216bfb0dde53d706 100644 --- a/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.ts +++ b/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.ts @@ -1,7 +1,7 @@ import { Directive, ElementRef, Input } from '@angular/core'; export const _verticalClasses: string[] = ['flex', 'flex-col']; -export const _horizontalClasses: string[] = ['flex', 'flex-row', 'flex-wrap']; +export const _horizontalClasses: string[] = ['flex', 'flex-wrap', 'gap-2']; export enum BinaryFileListOrientation { HORIZONTAL = 'horizontal', diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.html b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.html index 1bfa1fb53740999476b2e6b342777abec551f000..4024dc2de95e58d010447f113834a2b1b0387252 100644 --- a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.html +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.html @@ -4,10 +4,12 @@ [attr.data-test-id]="(label | convertForDataTest) + '-file-upload-button'" [multi]="true" [isLoading]="isUploadInProgress$ | async" - class="relative w-72" + [variant]="uploadButtonVariant" data-test-id="binary-file-upload" > <ods-spinner-icon spinner size="medium" /> <ods-attachment-icon icon size="medium" /> - <p text class="text-center">{{ label }}</p> + @if (label) { + <p text data-test-id="upload-button-label" class="text-center">{{ label }}</p> + } </ods-file-upload-button> diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.spec.ts b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.spec.ts index dc42e69a0f52518ba01f5f3ddf2009ef04e344e9..38303a464c9f128eb05963f94df756663567e129 100644 --- a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.spec.ts +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.spec.ts @@ -1,11 +1,17 @@ import { BinaryFileService, FileUploadType, ToUploadFile } from '@alfa-client/binary-file-shared'; import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; -import { existsAsHtmlElement, getElementComponentFromFixtureByCss, mock, Mock } from '@alfa-client/test-utils'; +import { + existsAsHtmlElement, + getElementComponentFromFixtureByCss, + mock, + Mock, + notExistsAsHtmlElement, +} from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { faker } from '@faker-js/faker/.'; import { expect } from '@jest/globals'; import { getUrl, Resource } from '@ngxp/rest'; -import { FileUploadButtonComponent, SpinnerIconComponent } from '@ods/system'; +import { FileUploadButtonComponent, SpinnerIconComponent, UploadButtonVariants } from '@ods/system'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; import { of } from 'rxjs'; @@ -23,6 +29,7 @@ describe('MultiFileUploadEditorComponent', () => { const uploadResource: Resource = createDummyResource([uploadLinkRel]); const buttonTestId: string = getDataTestIdOf('Ein_Label-file-upload-button'); + const buttonLabelTestId: string = getDataTestIdOf('upload-button-label'); let binaryFileService: Mock<BinaryFileService>; @@ -104,6 +111,24 @@ describe('MultiFileUploadEditorComponent', () => { } as ToUploadFile); }); }); + + describe('get uploadButtonVariant', () => { + it('should return "label"', () => { + component.label = 'test'; + + const result: UploadButtonVariants['variant'] = component.uploadButtonVariant; + + expect(result).toBe('label'); + }); + + it('should return "icon"', () => { + component.label = ''; + + const result: UploadButtonVariants['variant'] = component.uploadButtonVariant; + + expect(result).toBe('icon'); + }); + }); }); describe('template', () => { @@ -125,5 +150,22 @@ describe('MultiFileUploadEditorComponent', () => { expect(fileButtonComponent.isLoading).toEqual(true); }); }); + describe('upload button label', () => { + it('should show', () => { + component.label = 'test'; + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, buttonLabelTestId); + }); + + it('should hide', () => { + component.label = ''; + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, buttonLabelTestId); + }); + }); }); }); diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.ts b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.ts index 4c057b3f3d7b52b25dc63164e62f06bb054b83c8..2a9623612015f5350b39822c605c8faed1903d59 100644 --- a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.ts +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.ts @@ -5,7 +5,7 @@ import { AsyncPipe } from '@angular/common'; import { Component, HostListener, inject, Input, OnInit } from '@angular/core'; import { ControlContainer, FormGroupDirective, ReactiveFormsModule } from '@angular/forms'; import { getUrl, Resource } from '@ngxp/rest'; -import { AttachmentIconComponent, FileUploadButtonComponent, SpinnerIconComponent } from '@ods/system'; +import { AttachmentIconComponent, FileUploadButtonComponent, SpinnerIconComponent, UploadButtonVariants } from '@ods/system'; import { uniqueId } from 'lodash-es'; import { Observable } from 'rxjs'; @@ -55,4 +55,8 @@ export class MultiFileUploadEditorComponent implements OnInit { }); } } + + get uploadButtonVariant(): UploadButtonVariants['variant'] { + return this.label ? 'label' : 'icon'; + } } diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.html b/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.html index 184ac868f9f594da443f6f89b969ce1cc85b1dfc..8efd002c007d46dba8fc79c2526e59e132191c66 100644 --- a/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.html +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.html @@ -1,13 +1,15 @@ -<ods-file-upload-list-container - [parentFormArrayName]="filesFormFieldName" - [fileUploadType]="fileUploadType" - [filesResource]="filesResource" - [filesLinkRel]="filesLinkRelation" - data-test-id="file-list" -></ods-file-upload-list-container> -<ods-multi-file-upload-editor - [fileUploadType]="fileUploadType" - [uploadResource]="uploadResource" - [uploadLinkRelation]="uploadLinkRelation" - data-test-id="multi-file-upload-editor" -></ods-multi-file-upload-editor> \ No newline at end of file +<div class="flex flex-col gap-2"> + <ods-file-upload-list-container + [parentFormArrayName]="filesFormFieldName" + [fileUploadType]="fileUploadType" + [filesResource]="filesResource" + [filesLinkRel]="filesLinkRelation" + data-test-id="file-list" + /> + <ods-multi-file-upload-editor + [fileUploadType]="fileUploadType" + [uploadResource]="uploadResource" + [uploadLinkRelation]="uploadLinkRelation" + data-test-id="multi-file-upload-editor" + /> +</div> diff --git a/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.ts index a62e384fb3c9ff9c15b453cc9927438d1e1790c4..7de4a0bf3fc04340fa80daed1896235559f6dd18 100644 --- a/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.ts @@ -24,7 +24,7 @@ import { ConvertForDataTestPipe, isNotNil } from '@alfa-client/tech-shared'; import { Component, EventEmitter, HostListener, Input, Output } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; -import { FileUploadButtonComponent, SpinnerIconComponent } from '@ods/system'; +import { FileUploadButtonComponent } from '@ods/system'; import { uniqueId } from 'lodash-es'; import { FormControlEditorAbstractComponent } from '../formcontrol-editor.abstract.component'; @@ -32,7 +32,8 @@ import { FormControlEditorAbstractComponent } from '../formcontrol-editor.abstra selector: 'ods-single-file-upload-editor', templateUrl: './single-file-upload-editor.component.html', standalone: true, - imports: [FileUploadButtonComponent, SpinnerIconComponent, ReactiveFormsModule, ConvertForDataTestPipe], + styles: [':host {@apply contents}'], + imports: [FileUploadButtonComponent, ReactiveFormsModule, ConvertForDataTestPipe], }) export class SingleFileUploadEditorComponent extends FormControlEditorAbstractComponent { @Input() label: string = ''; diff --git a/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.spec.ts b/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.spec.ts index 8cbe8c1ae06eabaea31340ddd8eb31f710cab503..edbd1a28e05784e5de0448629fa12c6beaa41669 100644 --- a/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.spec.ts +++ b/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.spec.ts @@ -1,9 +1,11 @@ import { OzgCloudComponentFactory } from '@alfa-client/tech-shared'; import { dispatchEventFromFixture, Mock, mock, MockEvent, mockGetValue } from '@alfa-client/test-utils'; import { DIALOG_COMPONENT, OzgcloudDialogService } from '@alfa-client/ui'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; import { ComponentType } from '@angular/cdk/portal'; import { ComponentRef, Injector, ViewContainerRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { expect } from '@jest/globals'; import { ButtonComponent } from '@ods/system'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; @@ -24,6 +26,7 @@ describe('OpenDialogButtonComponent', () => { const componentRef: ComponentRef<any> = <any>{ instance: { constructor: null } }; const dialogResponse: any = {}; + const dummyDialogData: any = { someField: 'someValue' }; beforeEach(async () => { dialogComponent = {}; @@ -74,31 +77,27 @@ describe('OpenDialogButtonComponent', () => { }); it('should call dialog service to open dialog', () => { - dispatchEventFromFixture(fixture, openDialog, MockEvent.CLICK); - - expect(dialogService.openInContext).toHaveBeenCalledWith(componentRef.instance.constructor, viewContainerRef); - }); - }); + component.dialogData = dummyDialogData; - describe('open', () => { - beforeEach(() => { - component._createComponent = jest.fn().mockReturnValue(componentRef); - }); - - it('should emit close emitter on dialog close', () => { - component.close.emit = jest.fn(); - - component.open(); + dispatchEventFromFixture(fixture, openDialog, MockEvent.CLICK); - expect(component.close.emit).toHaveBeenCalled(); + expect(dialogService.openInContext).toHaveBeenCalledWith( + componentRef.instance.constructor, + viewContainerRef, + dummyDialogData, + ); }); }); describe('create component', () => { it('should call component factory to create component', () => { + component.dialogData = dummyDialogData; + component._createComponent(); - expect(componentFactory.createComponent).toHaveBeenCalledWith(dialogComponent, injector); + expect(componentFactory.createComponent).toHaveBeenCalledWith(dialogComponent, injector, [ + { provide: DIALOG_DATA, useValue: dummyDialogData }, + ]); }); }); }); diff --git a/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.ts b/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.ts index 2815aab6fd3ed8601930bfe061fecc23fc0e4704..974cffb092526ebbc9bf0839b057ea0dde1297ec 100644 --- a/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.ts +++ b/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.ts @@ -1,19 +1,21 @@ import { OzgCloudComponentFactory } from '@alfa-client/tech-shared'; import { DIALOG_COMPONENT, OzgcloudDialogService } from '@alfa-client/ui'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; import { ComponentType } from '@angular/cdk/portal'; -import { Component, ComponentRef, EventEmitter, inject, Injector, Input, Output, ViewContainerRef } from '@angular/core'; +import { Component, ComponentRef, inject, Injector, Input, ViewContainerRef } from '@angular/core'; import { ButtonComponent, ButtonVariants } from '@ods/system'; -import { first } from 'rxjs'; @Component({ selector: 'ods-open-dialog-button', standalone: true, imports: [ButtonComponent], - template: `<ods-button + template: ` <ods-button (clickEmitter)="open()" [variant]="variant" [text]="label" + [size]="size" [dataTestId]="dataTestId" + [dataTestClass]="dataTestClass" data-test-id="open-dialog" > <ng-container icon> @@ -32,18 +34,18 @@ export class OpenDialogButtonComponent { @Input() label: string; @Input() dataTestId: string; + @Input() dataTestClass: string; @Input() variant: ButtonVariants['variant'] = 'primary'; - - @Output() close: EventEmitter<void> = new EventEmitter(); + @Input() dialogData: any; + @Input() size: ButtonVariants['size']; public open(): void { - this.dialogService - .openInContext(this._createComponent().instance.constructor, this.viewContainerRef) - .closed.pipe(first()) - .subscribe(this.close.emit); + this.dialogService.openInContext(this._createComponent().instance.constructor, this.viewContainerRef, this.dialogData); } _createComponent(): ComponentRef<any> { - return this.componentFactory.createComponent<any>(this.component, this.injector); + return this.componentFactory.createComponent<any>(this.component, this.injector, [ + { provide: DIALOG_DATA, useValue: this.dialogData }, + ]); } } diff --git a/alfa-client/libs/design-system/src/lib/button/button.component.ts b/alfa-client/libs/design-system/src/lib/button/button.component.ts index c0f7892c3c2c5184571b175dbff2326591415495..47e76172adb5e6f9600be54291ee79a841defb78 100644 --- a/alfa-client/libs/design-system/src/lib/button/button.component.ts +++ b/alfa-client/libs/design-system/src/lib/button/button.component.ts @@ -96,6 +96,7 @@ export type ButtonVariants = VariantProps<typeof buttonVariants>; [attr.aria-disabled]="isDisabled" [attr.aria-label]="text" [attr.data-test-id]="dataTestId" + [attr.data-test-class]="dataTestClass" (click)="clickEmitter.emit()" > <ng-content *ngIf="!isLoading" select="[icon]"></ng-content> @@ -106,6 +107,7 @@ export type ButtonVariants = VariantProps<typeof buttonVariants>; export class ButtonComponent { @Input() text: string = ''; @Input() dataTestId: string = ''; + @Input() dataTestClass: string = ''; @Input() disabled: boolean = false; @Input() isLoading: boolean = false; @Input({ transform: booleanAttribute }) destructive: boolean = false; diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.spec.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.spec.ts index 0b747f1e0507e5375a8dd0baba6a7ce2733ba89c..1af5235f276bf8afd8787fd155e104f3a8b64040 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.spec.ts @@ -43,13 +43,13 @@ describe('DropdownMenuButtonItemComponent', () => { expect(component).toBeTruthy(); }); - describe('itemClicked emitter', () => { - it('should emit itemClicked', () => { - component.itemClicked.emit = jest.fn(); + describe('clickEmitter', () => { + it('should emit', () => { + component.clickEmitter.emit = jest.fn(); dispatchEventFromFixture(fixture, 'button', 'click'); - expect(component.itemClicked.emit).toHaveBeenCalled(); + expect(component.clickEmitter.emit).toHaveBeenCalled(); }); }); }); diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.ts index 2c1b5427927141d6b7ca40515ee64432d9d50e2e..85f7e1f0f6895e6798d6697f3e7e63391be8ca14 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.ts @@ -28,19 +28,21 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; selector: 'ods-dropdown-menu-button-item', standalone: true, imports: [CommonModule], - template: `<button - class="flex min-h-12 w-full items-center gap-4 border-2 border-transparent px-4 py-3 text-start outline-none hover:border-primary focus-visible:border-focus" - role="menuitem" - (click)="itemClicked.emit()" - [attr.data-test-id]="dataTestId" - > - <ng-content select="[icon]" /> - <p class="text-text">{{ caption }}</p> - </button>`, + template: ` <div class="w-full bg-whitetext p-1.5"> + <button + class="flex w-full items-center gap-2 rounded-md border border-transparent px-4 py-2 text-start font-medium outline-none hover:bg-background-150 focus-visible:border-primary dark:hover:bg-neutral-700" + role="menuitem" + (click)="clickEmitter.emit()" + [attr.data-test-id]="dataTestId" + > + <ng-content select="[icon]" /> + <p class="text-sm text-primary">{{ caption }}</p> + </button> + </div>`, }) export class DropdownMenuButtonItemComponent { @Input({ required: true }) caption!: string; @Input() dataTestId: string; - @Output() itemClicked: EventEmitter<MouseEvent> = new EventEmitter(); + @Output() clickEmitter: EventEmitter<MouseEvent> = new EventEmitter(); } diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component.ts index 301af6719e17a63de18c5141a3bec564265976cd..2314cab202a68e6d276c7d0a3a731ae15df5c239 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component.ts @@ -7,6 +7,6 @@ import { Component } from '@angular/core'; imports: [CommonModule], styles: [':host {@apply block min-h-12 px-4 py-3 first:mt-2 last:mb-2}'], - template: ` <ng-content /> `, + template: `<ng-content />`, }) export class DropdownMenuItemComponent {} diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component.ts index 045191b56a76618a85600a82757607f267b4c8e4..24427bb02e4ed24c83d01a0c4c942b474ef7385a 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from '@angular/common'; import { Component, Input } from '@angular/core'; import { OpenLinkIconComponent } from '../../icons/open-link-icon/open-link-icon.component'; import { LinkComponent } from '../../link/link.component'; @@ -5,17 +6,27 @@ import { LinkComponent } from '../../link/link.component'; @Component({ selector: 'ods-dropdown-menu-link-item', standalone: true, - imports: [OpenLinkIconComponent, LinkComponent], - styles: [':host {@apply first:mt-2}'], - template: ` <ods-link [url]="url" class="bg-whitetext" [openInNewTab]="true"> - <div class="flex items-center gap-2 px-4 py-3"> - <p class="font-medium text-primary">{{ text }}</p> - <ods-open-link-icon class="size-5" /> - <span class="sr-only">Öffnet in einem neuen Tab</span> - </div> - </ods-link>`, + imports: [LinkComponent, OpenLinkIconComponent, CommonModule], + template: `<div class="w-full bg-whitetext p-1.5"> + <ods-link [url]="url" [openInNewTab]="true"> + <div class="flex min-w-80 gap-3 px-3 py-1.5"> + <ng-content select="[icon]" /> + <div class="flex flex-col gap-1"> + @if (caption) { + <p class="text-sm font-medium text-text">{{ caption }}</p> + } + <div class="flex items-center gap-2"> + <p class="text-sm font-normal text-primary">{{ text }}</p> + <ods-open-link-icon size="small" /> + <span class="sr-only">Öffnet in einem neuen Tab</span> + </div> + </div> + </div> + </ods-link> + </div>`, }) export class DropdownMenuLinkItemComponent { @Input() url: string; + @Input() caption: string; @Input() text: string; } diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu/dropdown-menu.component.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu/dropdown-menu.component.ts index 04a15c7eec0780ac9b8709224ab54aecaf77ab6f..24c78fceb054182a65572cef755fa379dddca255 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu/dropdown-menu.component.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu/dropdown-menu.component.ts @@ -46,9 +46,10 @@ import { twMerge } from 'tailwind-merge'; > <ng-content select="[button-content]" /> </button> + <div *ngIf="isPopupOpen" - class="absolute z-50 max-h-120 min-w-44 max-w-96 animate-fadeIn overflow-y-auto rounded bg-dropdownBg shadow-md ring-1 ring-grayborder focus:outline-none" + class="absolute z-50 mt-2 min-w-44 max-w-96 animate-fadeIn rounded-lg bg-background-100 ring-1 ring-grayborder drop-shadow-lg focus:outline-none" [ngClass]="alignTo === 'left' ? 'right-0' : 'left-0'" role="menu" aria-modal="true" diff --git a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.html b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.html index dc2b6cef68ea7358023c7791fea0eb6efbaf47bf..b65b88d5b02c74982b872549c6d87aeccdf1644b 100644 --- a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.html +++ b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.html @@ -34,14 +34,13 @@ [multiple]="multi" [attr.data-test-id]="(id | convertForDataTest) + '-file-upload-input'" /> -<label - [for]="id" - class="z-10 inline-flex w-full flex-grow items-center justify-start gap-4 break-words rounded-md bg-background-50 py-3 pl-6 pr-6 text-text hover:bg-background-100 focus:outline-none focus:ring-2 focus:ring-primary peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ozgblue-800 peer-disabled:cursor-wait peer-disabled:hover:bg-background-50" - role="button" -> - <ng-content *ngIf="!isLoading" select="[icon]"></ng-content> - <ng-content *ngIf="isLoading" select="[spinner]"></ng-content> - <div class="flex-grow"> +<label [for]="id" [ngClass]="uploadButtonVariants({ variant })" role="button"> + @if (isLoading) { + <ng-content select="[spinner]"></ng-content> + } @else { + <ng-content select="[icon]"></ng-content> + } + <div class="flex-grow empty:hidden"> <ng-content select="[text]"></ng-content> </div> </label> diff --git a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.ts b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.ts index 2371918f9cdba62e0ea0ee3a5e315ee9f1d9e378..b5048be11b1b7e67eaf8c077e3d4cd0caab66388 100644 --- a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.ts +++ b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.ts @@ -24,12 +24,33 @@ import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; import { Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { cva, VariantProps } from 'class-variance-authority'; + +export const uploadButtonVariants = cva( + [ + 'z-10 inline-flex flex-grow items-center justify-start gap-4 break-words rounded-md text-primary', + 'border border-transparent hover:bg-ghost-hover peer-focus-visible:border-background-200', + 'peer-focus-visible:outline peer-focus-visible:outline-focus peer-focus-visible:bg-ghost-hover peer-focus-visible:outline-offset-1', + ], + { + variants: { + variant: { + label: 'py-3 px-6 bg-background-50 w-full', + icon: 'p-2 w-fit', + }, + }, + defaultVariants: { + variant: 'label', + }, + }, +); +export type UploadButtonVariants = VariantProps<typeof uploadButtonVariants>; @Component({ selector: 'ods-file-upload-button', standalone: true, imports: [CommonModule, ConvertForDataTestPipe], - styles: [':host {@apply inline-flex}'], + styles: [':host {@apply relative}'], templateUrl: './file-upload-button.component.html', }) export class FileUploadButtonComponent { @@ -37,9 +58,12 @@ export class FileUploadButtonComponent { @Input() isLoading: boolean = false; @Input() accept: string = '*/*'; @Input() multi: boolean = false; + @Input() variant: UploadButtonVariants['variant']; @ViewChild('inputElement') inputElement: ElementRef = new ElementRef({}); + readonly uploadButtonVariants = uploadButtonVariants; + resetInput(): void { this.inputElement.nativeElement.value = ''; } diff --git a/alfa-client/libs/design-system/src/lib/icons/file-icon/file-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/file-icon/file-icon.component.ts index 2c084618d209a629d826c35f1397e8ff6c1de6d0..1e3a7ec67b3136e428a177080bd99f7410cb6ae1 100644 --- a/alfa-client/libs/design-system/src/lib/icons/file-icon/file-icon.component.ts +++ b/alfa-client/libs/design-system/src/lib/icons/file-icon/file-icon.component.ts @@ -38,6 +38,7 @@ const fileiconVariants = cva('fill-ozggray-300', { }, size: { small: 'w-4 h-5', + medium: 'w-5 h-6', large: 'w-8 h-10', }, }, diff --git a/alfa-client/libs/design-system/src/lib/link/link.component.ts b/alfa-client/libs/design-system/src/lib/link/link.component.ts index 64830deee7cf6873156013281ec33d7e2a56199e..a7f529ad5ca57b02e72dcb6033b128a9c2e77416 100644 --- a/alfa-client/libs/design-system/src/lib/link/link.component.ts +++ b/alfa-client/libs/design-system/src/lib/link/link.component.ts @@ -11,7 +11,7 @@ import { twMerge } from 'tailwind-merge'; [href]="url" [class]=" twMerge( - 'block rounded-lg border-2 border-transparent text-text hover:bg-ghost-hover focus-visible:border-focus focus-visible:bg-ghost-hover focus-visible:outline-none dark:hover:bg-neutral-700', + 'block rounded-md border border-transparent text-text hover:bg-neutral-100 focus-visible:border-primary focus-visible:outline-none dark:hover:bg-neutral-700', class ) " diff --git a/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.spec.ts b/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.spec.ts index 98a918c03eb9d525d9bc2977f1885838dd873da3..1ceecd9e1f16e3309e234235887150f8e5c6ee3a 100644 --- a/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.spec.ts +++ b/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.spec.ts @@ -24,16 +24,19 @@ import { BinaryFileService } from '@alfa-client/binary-file-shared'; import { CommandOrder, CommandResource, CommandService, CreateCommand } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { createEmptyStateResource, createStateResource, StateResource } from '@alfa-client/tech-shared'; +import { createEmptyStateResource, createStateResource, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { VorgangService, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; +import { faker } from '@faker-js/faker'; import { expect } from '@jest/globals'; +import { ResourceUri } from '@ngxp/rest'; import { cold, hot } from 'jest-marbles'; import { CommandLinkRel } from 'libs/command-shared/src/lib/command.linkrel'; import { createCommandResource } from 'libs/command-shared/test/command'; import { createKommentar, createKommentarListResource, createKommentarResource } from 'libs/kommentar-shared/test/kommentar'; import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; import { of } from 'rxjs'; +import { singleCold } from '../../../tech-shared/test/marbles'; import { KommentarLinkRel, KommentarListLinkRel } from './kommentar.linkrel'; import { Kommentar, KOMMENTAR_UPLOADED_ATTACHMENTS, KommentarListResource, KommentarResource } from './kommentar.model'; import { KommentarRepository } from './kommentar.repository'; @@ -327,4 +330,78 @@ describe('KommentarService', () => { expect(binaryFileService.clearUploadedFiles).toHaveBeenCalledWith(KOMMENTAR_UPLOADED_ATTACHMENTS); }); }); + + describe('is new kommentar formular visible', () => { + it('should emit true', () => { + service._currentlyEdited$.next(EMPTY_STRING); + service.formularVisibility$.next(true); + + expect(service.isFormularVisible()).toBeObservable(singleCold(true)); + }); + + it('should emit false if any kommentar is being edited', () => { + service._currentlyEdited$.next(faker.internet.url()); + service.formularVisibility$.next(true); + + expect(service.isFormularVisible()).toBeObservable(singleCold(false)); + }); + + it('should emit false if not visible', () => { + service._currentlyEdited$.next(EMPTY_STRING); + service.formularVisibility$.next(false); + + expect(service.isFormularVisible()).toBeObservable(singleCold(false)); + }); + }); + + describe('show new kommentar formular', () => { + beforeEach(() => { + service.setCurrentlyEdited = jest.fn(); + }); + + it('should set currently edited to empty string', () => { + service.showFormular(); + + expect(service.setCurrentlyEdited).toHaveBeenCalledWith(EMPTY_STRING); + }); + + it('should set formular visibility', () => { + service.showFormular(); + + expect(service.formularVisibility$).toBeObservable(singleCold(true)); + }); + }); + + describe('set currently edited kommentar uri', () => { + beforeEach(() => { + service.clearUploadedFiles = jest.fn(); + service.hideFormular = jest.fn(); + }); + + it('should clear uploaded files', () => { + service.setCurrentlyEdited(faker.internet.url()); + + expect(service.clearUploadedFiles).toHaveBeenCalled(); + }); + + it('should hide kommentar creation formular', () => { + service.setCurrentlyEdited(faker.internet.url()); + + expect(service.hideFormular).toHaveBeenCalled(); + }); + + it('should NOT hide kommentar creation formular', () => { + service.setCurrentlyEdited(EMPTY_STRING); + + expect(service.hideFormular).not.toHaveBeenCalled(); + }); + + it('should emit currently edited resource uri', () => { + const resourceUri: ResourceUri = faker.internet.url(); + + service.setCurrentlyEdited(resourceUri); + + expect(service._currentlyEdited$).toBeObservable(singleCold(resourceUri)); + }); + }); }); diff --git a/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.ts b/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.ts index 27efa8ae9da9e852906f3917b070e27e9faeca2d..6749d94ff0079091abf5f3a42d6c67786b171d08 100644 --- a/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.ts +++ b/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.ts @@ -24,13 +24,13 @@ import { BinaryFileListResource, BinaryFileService } from '@alfa-client/binary-file-shared'; import { CommandOrder, CommandResource, CommandService, CreateCommand, isDone } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { createEmptyStateResource, createStateResource, doIfLoadingRequired, StateResource } from '@alfa-client/tech-shared'; +import { createEmptyStateResource, createStateResource, doIfLoadingRequired, EMPTY_STRING, isNotEmpty, StateResource, } from '@alfa-client/tech-shared'; import { VorgangResource, VorgangService } from '@alfa-client/vorgang-shared'; import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; -import { hasLink, Resource } from '@ngxp/rest'; +import { hasLink, Resource, ResourceUri } from '@ngxp/rest'; import { isNil } from 'lodash-es'; -import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { map, startWith, tap } from 'rxjs/operators'; import { KommentarLinkRel, KommentarListLinkRel } from './kommentar.linkrel'; import { Kommentar, KOMMENTAR_UPLOADED_ATTACHMENTS, KommentarListResource, KommentarResource } from './kommentar.model'; @@ -42,6 +42,7 @@ export class KommentarService { createEmptyStateResource<KommentarListResource>(), ); readonly formularVisibility$: BehaviorSubject<boolean> = new BehaviorSubject(false); + readonly _currentlyEdited$: BehaviorSubject<ResourceUri> = new BehaviorSubject(''); private navigationSub: Subscription; @@ -104,7 +105,9 @@ export class KommentarService { } public isFormularVisible(): Observable<boolean> { - return this.formularVisibility$.asObservable(); + return combineLatest([this.formularVisibility$, this._currentlyEdited$]).pipe( + map(([isVisible, currentlyEdited]) => isVisible && currentlyEdited === EMPTY_STRING), + ); } public canCreateNewKommentar(kommentareListResource: KommentarListResource): Observable<boolean> { @@ -114,6 +117,7 @@ export class KommentarService { } public showFormular(): void { + this.setCurrentlyEdited(EMPTY_STRING); this.formularVisibility$.next(true); } @@ -170,4 +174,16 @@ export class KommentarService { public clearUploadedFiles(): void { this.binaryFileService.clearUploadedFiles(KOMMENTAR_UPLOADED_ATTACHMENTS); } + + public getCurrentlyEdited(): Observable<ResourceUri> { + return this._currentlyEdited$.asObservable(); + } + + public setCurrentlyEdited(resourceUri: ResourceUri): void { + this.clearUploadedFiles(); + if (isNotEmpty(resourceUri)) { + this.hideFormular(); + } + this._currentlyEdited$.next(resourceUri); + } } diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.html b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.html index 8b432f5eee5589efc42249c5bfdf3f64a631b818..30f142acca7a7374f7e11d554c238f72a235b1ae 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.html +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.html @@ -27,6 +27,7 @@ <ozgcloud-expansion-panel headline="Kommentare"> <alfa-kommentar-list-in-vorgang [kommentarListStateResource]="kommentarListStateResource" + [currentlyEdited]="kommentarService.getCurrentlyEdited() | async" data-test-id="kommentar-list-in-vorgang" > </alfa-kommentar-list-in-vorgang> diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.spec.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.spec.ts index 44794b0072f9b661cd22120938e0a045e9b5d330..1fa11168673776f20130a7be5616abb0ac84380a 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.spec.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.spec.ts @@ -70,6 +70,14 @@ describe('KommentarListInVorgangContainerComponent', () => { expect(component).toBeTruthy(); }); + describe('ng on init', () => { + it('should call kommentar service isFormularVisible', () => { + component.ngOnInit(); + + expect(kommentarService.isFormularVisible).toHaveBeenCalled(); + }); + }); + describe('ng on changes', () => { beforeEach(() => { kommentarService.isFormularVisible.mockReturnValue(new Observable((o) => o.next(false))); @@ -78,12 +86,6 @@ describe('KommentarListInVorgangContainerComponent', () => { ); }); - it('should call kommentar service isFormularVisible', () => { - component.ngOnChanges(); - - expect(kommentarService.isFormularVisible).toHaveBeenCalled(); - }); - it('should call kommentar service getKommentareByVorgang', () => { component.ngOnChanges(); diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.ts index 8c601d9240a060ca4ceb9cd18a9b9964198eae83..21d3159a29496a6927f7076de4734ff03c663f44 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.ts @@ -21,10 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input, OnChanges } from '@angular/core'; import { KommentarListResource, KommentarService } from '@alfa-client/kommentar-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; +import { Component, inject, Input, OnChanges, OnInit } from '@angular/core'; import { mergeMap, Observable } from 'rxjs'; @Component({ @@ -32,25 +32,24 @@ import { mergeMap, Observable } from 'rxjs'; templateUrl: './kommentar-list-in-vorgang-container.component.html', styleUrls: ['./kommentar-list-in-vorgang-container.component.scss'], }) -export class KommentarListInVorgangContainerComponent implements OnChanges { +export class KommentarListInVorgangContainerComponent implements OnChanges, OnInit { @Input() vorgangStateResource: StateResource<VorgangWithEingangResource>; - showFormular$: Observable<boolean>; + public readonly kommentarService = inject(KommentarService); + + public showFormular$: Observable<boolean>; kommentarListStateResource$: Observable<StateResource<KommentarListResource>>; canCreateNewKommentar$: Observable<boolean>; - constructor(private kommentarService: KommentarService) {} + ngOnInit(): void { + this.showFormular$ = this.kommentarService.isFormularVisible(); + } ngOnChanges(): void { this.reloadKommentarListOnVorgangReload(); - this.showFormular$ = this.kommentarService.isFormularVisible(); - this.kommentarListStateResource$ = this.kommentarService.getKommentareByVorgang( - this.vorgangStateResource.resource, - ); + this.kommentarListStateResource$ = this.kommentarService.getKommentareByVorgang(this.vorgangStateResource.resource); this.canCreateNewKommentar$ = this.kommentarListStateResource$.pipe( - mergeMap((stateResource) => - this.kommentarService.canCreateNewKommentar(stateResource.resource), - ), + mergeMap((stateResource) => this.kommentarService.canCreateNewKommentar(stateResource.resource)), ); } diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.html b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.html index be3ecc3720ffb2e967b888e54243eb72a5234c11..75ccfb30068aad3b5d8e2847618a3267cb0fb400 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.html +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.html @@ -27,5 +27,6 @@ *ngFor="let kommentar of kommentare" [kommentar]="kommentar" [kommentarListStateResource]="kommentarListStateResource" + [currentlyEdited]="currentlyEdited" > </alfa-kommentar-list-item-in-vorgang> diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.ts index 4519d517cefe76995824afd7b45b8651ac354b8a..1d7212702a1b398d0da13637dc24e598d1f5bf8d 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.ts @@ -21,10 +21,11 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input, OnChanges } from '@angular/core'; import { KommentarListResource, KommentarResource } from '@alfa-client/kommentar-shared'; -import { KommentarListLinkRel } from 'libs/kommentar-shared/src/lib/kommentar.linkrel'; import { getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; +import { Component, Input, OnChanges } from '@angular/core'; +import { ResourceUri } from '@ngxp/rest'; +import { KommentarListLinkRel } from 'libs/kommentar-shared/src/lib/kommentar.linkrel'; @Component({ selector: 'alfa-kommentar-list-in-vorgang', @@ -33,17 +34,15 @@ import { getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; }) export class KommentarListInVorgangComponent implements OnChanges { @Input() kommentarListStateResource: StateResource<KommentarListResource>; + @Input() currentlyEdited: ResourceUri; - kommentare: KommentarResource[]; + public kommentare: KommentarResource[]; ngOnChanges(): void { this.kommentare = this.getKommentare(); } getKommentare(): KommentarResource[] { - return getEmbeddedResources( - this.kommentarListStateResource, - KommentarListLinkRel.KOMMENTAR_LIST, - ); + return getEmbeddedResources(this.kommentarListStateResource, KommentarListLinkRel.KOMMENTAR_LIST); } } diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.spec.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.spec.ts index 127b7fda5dd6f18ac336393aa425dd3903e94ee7..c5a1d190d2f95214dd39f60e95119fbda5d9e9ba 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.spec.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.spec.ts @@ -21,26 +21,17 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { KommentarLinkRel, KommentarResource, KommentarService } from '@alfa-client/kommentar-shared'; +import { ConvertForDataTestPipe, FormatDateWithTimePipe, HasLinkPipe } from '@alfa-client/tech-shared'; +import { getElementFromFixture, mock } from '@alfa-client/test-utils'; +import { UserProfileInKommentarContainerComponent } from '@alfa-client/user-profile'; import { registerLocaleData } from '@angular/common'; import localeDe from '@angular/common/locales/de'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { - KommentarLinkRel, - KommentarListLinkRel, - KommentarService, -} from '@alfa-client/kommentar-shared'; -import { - ConvertForDataTestPipe, - createStateResource, - FormatDateWithTimePipe, - HasLinkPipe, -} from '@alfa-client/tech-shared'; -import { getElementFromFixture, mock } from '@alfa-client/test-utils'; -import { UserProfileInKommentarContainerComponent } from '@alfa-client/user-profile'; -import { - createKommentarListResource, - createKommentarResource, -} from 'libs/kommentar-shared/test/kommentar'; +import { faker } from '@faker-js/faker/.'; +import { expect } from '@jest/globals'; +import { getUrl } from '@ngxp/rest'; +import { createKommentarResource } from 'libs/kommentar-shared/test/kommentar'; import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; import { KommentarFormComponent } from '../../kommentar-form/kommentar-form.component'; @@ -85,6 +76,37 @@ describe('KommentarListItemInVorgangComponent', () => { expect(component).toBeTruthy(); }); + describe('set currently edited', () => { + const kommentar: KommentarResource = createKommentarResource(); + + it('should set edit mode to false on same currently edited resource uri', () => { + component.editMode = false; + component.kommentar = kommentar; + + component.currentlyEdited = getUrl(kommentar); + + expect(component.editMode).toBe(false); + }); + + it('should set edit mode to false on different currently edited resource uri', () => { + component.editMode = true; + component.kommentar = kommentar; + + component.currentlyEdited = faker.internet.url(); + + expect(component.editMode).toBe(false); + }); + + it('should set edit mode to true', () => { + component.editMode = true; + component.kommentar = kommentar; + + component.currentlyEdited = getUrl(kommentar); + + expect(component.editMode).toBe(true); + }); + }); + describe('user profile', () => { it('should be visible on existing link', () => { component.kommentar = createKommentarResource([KommentarLinkRel.CREATED_BY]); @@ -105,18 +127,9 @@ describe('KommentarListItemInVorgangComponent', () => { }); }); - describe('kommentare attachments', () => { - it('should be loaded', () => { - const kommentarResource = createKommentarResource(); - component.kommentar = kommentarResource; - - component.ngOnInit(); - - expect(kommentarService.getAttachments).toHaveBeenCalledWith(kommentarResource); - }); - }); - describe('edit', () => { + const kommentar: KommentarResource = createKommentarResource(); + it('should change editMode', () => { component.kommentar = createKommentarResource([KommentarLinkRel.EDIT]); @@ -125,12 +138,20 @@ describe('KommentarListItemInVorgangComponent', () => { expect(component.editMode).toBeTruthy(); }); - it('should not change editMode', () => { - component.kommentar = createKommentarResource(); + it('should NOT change editMode', () => { + component.kommentar = kommentar; component.edit(); expect(component.editMode).toBeFalsy(); }); + + it('should set currently edited kommetar uri', () => { + component.kommentar = kommentar; + + component.edit(); + + expect(kommentarService.setCurrentlyEdited).toHaveBeenCalledWith(getUrl(kommentar)); + }); }); }); diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.ts index cf6a951f4bd32c241281ce7c3207092387f92ce1..7da6b91741e10ca74fdb3a4572d43190481826d5 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.ts @@ -21,35 +21,32 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { BinaryFileListResource } from '@alfa-client/binary-file-shared'; import { KommentarLinkRel, KommentarListResource, KommentarResource, KommentarService } from '@alfa-client/kommentar-shared'; -import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; -import { Component, Input, OnInit } from '@angular/core'; -import { hasLink } from '@ngxp/rest'; -import { Observable, of } from 'rxjs'; +import { StateResource } from '@alfa-client/tech-shared'; +import { Component, inject, Input } from '@angular/core'; +import { getUrl, hasLink, ResourceUri } from '@ngxp/rest'; @Component({ selector: 'alfa-kommentar-list-item-in-vorgang', templateUrl: './kommentar-list-item-in-vorgang.component.html', styleUrls: ['./kommentar-list-item-in-vorgang.component.scss'], }) -export class KommentarListItemInVorgangComponent implements OnInit { +export class KommentarListItemInVorgangComponent { @Input() kommentar: KommentarResource; - @Input() kommentarListStateResource: StateResource<KommentarListResource>; - attachments$: Observable<StateResource<BinaryFileListResource>> = of(createEmptyStateResource<BinaryFileListResource>()); - editMode: boolean = false; + @Input() set currentlyEdited(value: ResourceUri) { + this.editMode &&= value === getUrl(this.kommentar); + } - readonly kommentarLinkRel = KommentarLinkRel; + public kommentarService = inject(KommentarService); - constructor(public kommentarService: KommentarService) {} + public editMode: boolean = false; - ngOnInit(): void { - this.attachments$ = this.kommentarService.getAttachments(this.kommentar); - } + public readonly kommentarLinkRel = KommentarLinkRel; - edit(): void { + public edit(): void { this.editMode = hasLink(this.kommentar, KommentarLinkRel.EDIT); + this.kommentarService.setCurrentlyEdited(getUrl(this.kommentar)); } } diff --git a/alfa-client/libs/tech-shared/src/lib/service/component.factory.spec.ts b/alfa-client/libs/tech-shared/src/lib/service/component.factory.spec.ts index 32fb2f6d84f6be4f3699b21359aa61395a0d50f8..d8a94aec3ede53c078234f2b4e75cde96aeedbdb 100644 --- a/alfa-client/libs/tech-shared/src/lib/service/component.factory.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/service/component.factory.spec.ts @@ -1,8 +1,9 @@ import { Mock, mock, mockGetValue } from '@alfa-client/test-utils'; -import { ApplicationRef, ComponentRef, EnvironmentInjector, Injector, ViewRef } from '@angular/core'; +import { ApplicationRef, ComponentRef, EnvironmentInjector, Injector, Provider, ViewRef } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { OzgCloudComponentFactory } from './component.factory'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; import { ComponentType } from '@angular/cdk/portal'; jest.mock('@angular/core', () => ({ @@ -16,6 +17,8 @@ describe('OzgCloudComponentFactory', () => { let appRef: Mock<ApplicationRef>; let envInjector: Mock<EnvironmentInjector>; + const dummyProvider: Provider = { provide: DIALOG_DATA, useValue: { someField: 'value' } } as Provider; + beforeEach(() => { appRef = mock(ApplicationRef); envInjector = mock(EnvironmentInjector as any); @@ -45,7 +48,7 @@ describe('OzgCloudComponentFactory', () => { }); it('should call createComponent', () => { - factory.createComponent(componentType, injector); + factory.createComponent(componentType, injector, [dummyProvider]); expect(createComponentSpy).toHaveBeenCalledWith(componentType, { environmentInjector: envInjector, @@ -76,7 +79,7 @@ describe('OzgCloudComponentFactory', () => { }); it('should create an injector by given parent selector', () => { - factory._createElementInjector(injectorMock); + factory._createElementInjector(injectorMock, [dummyProvider]); expect(createSpy).toHaveBeenCalled(); }); diff --git a/alfa-client/libs/tech-shared/src/lib/service/component.factory.ts b/alfa-client/libs/tech-shared/src/lib/service/component.factory.ts index e54497e9fc92bbb198891d487ddcf7565ffab139..b3d13c11b4897ff1204a3d565edfb18f7bee5b9a 100644 --- a/alfa-client/libs/tech-shared/src/lib/service/component.factory.ts +++ b/alfa-client/libs/tech-shared/src/lib/service/component.factory.ts @@ -1,22 +1,35 @@ import { ComponentType } from '@angular/cdk/portal'; -import { ApplicationRef, ComponentRef, createComponent, EnvironmentInjector, inject, Injectable, Injector } from '@angular/core'; +import { + ApplicationRef, + ComponentRef, + createComponent, + EnvironmentInjector, + inject, + Injectable, + Injector, + Provider, +} from '@angular/core'; @Injectable({ providedIn: 'root' }) export class OzgCloudComponentFactory { private readonly appRef = inject(ApplicationRef); private readonly envInjector = inject(EnvironmentInjector); - public createComponent<T>(componentType: ComponentType<any>, parentInjector: Injector): ComponentRef<T> { + public createComponent<T>( + componentType: ComponentType<any>, + parentInjector: Injector, + providers: Provider[] = [], + ): ComponentRef<T> { const component: ComponentRef<any> = <ComponentRef<any>>createComponent(componentType, { environmentInjector: this.envInjector, - elementInjector: this._createElementInjector(parentInjector), + elementInjector: this._createElementInjector(parentInjector, providers), }); this._registerComponentToChangeDetection(component); return component; } - _createElementInjector(parentInjector: Injector): Injector { - return Injector.create({ providers: [], parent: parentInjector }); + _createElementInjector(parentInjector: Injector, providers: Provider[] = []): Injector { + return Injector.create({ providers, parent: parentInjector }); } _registerComponentToChangeDetection(component: ComponentRef<any>): void { diff --git a/alfa-client/libs/tech-shared/test/data-test.ts b/alfa-client/libs/tech-shared/test/data-test.ts index 795b2dee30c083f71da5842c2cc64738c3dd9da4..c4fbe01458d666825688364aee1f75b9fac3ea4e 100644 --- a/alfa-client/libs/tech-shared/test/data-test.ts +++ b/alfa-client/libs/tech-shared/test/data-test.ts @@ -35,10 +35,9 @@ export function getDataTestIdOf(value: string): string { return `[data-test-id="${value}"]`; } +/** + * @deprecated use getDataTestIfOf instead and a direct data-test-id at the component + */ export function getDataTestIdAttributeOf(value: string): string { return `[dataTestId="${value}"]`; } - -export function getDynamicDataTestIdAttributOf(value: string): string { - return `[ng-reflect-data-test-id="${value}"]`; -} diff --git a/alfa-client/libs/test-utils/src/lib/dialog.ts b/alfa-client/libs/test-utils/src/lib/dialog.ts index 7e09b7bea501d2f2d52bd1b696a7f27d975efb4f..5a8c5fe4737efc92501377610d333e50af98f5d8 100644 --- a/alfa-client/libs/test-utils/src/lib/dialog.ts +++ b/alfa-client/libs/test-utils/src/lib/dialog.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { jest } from '@jest/globals'; -import { EMPTY, Observable } from 'rxjs'; +import { EMPTY, Observable, of } from 'rxjs'; export class DialogRefMock<R = unknown> { public keydownEvents: Observable<KeyboardEvent> = EMPTY; @@ -34,3 +34,9 @@ export class DialogRefMock<R = unknown> { export function createDialogRefMock<R = unknown>(): DialogRefMock<R> { return new DialogRefMock(); } + +export function createdClosedDialogRefMock<R = unknown>(result?: R): DialogRefMock<R> { + const dialogRefMock = createDialogRefMock<R>(); + dialogRefMock.closed = of(result); + return dialogRefMock; +} diff --git a/alfa-client/libs/test-utils/src/lib/helper.ts b/alfa-client/libs/test-utils/src/lib/helper.ts index e847268dea4139a32915ce071ddd79fb20da4e39..66f33c3579e18d88743d3b23ea96703a2799bb04 100644 --- a/alfa-client/libs/test-utils/src/lib/helper.ts +++ b/alfa-client/libs/test-utils/src/lib/helper.ts @@ -55,6 +55,11 @@ export function dispatchEventFromFixture<T>(fixture: ComponentFixture<T>, elemen element.nativeElement.dispatchEvent(new Event(event)); } +export function dispatchEventFromFixtureByType<T, C>(fixture: ComponentFixture<T>, component: Type<C>, event: string): void { + const element: DebugElement = getDebugElementFromFixtureByType(fixture, component); + element.nativeElement.dispatchEvent(new Event(event)); +} + export function triggerEvent<T>(eventData: EventData<T>) { const element: DebugElement = getDebugElementFromFixtureByCss(eventData.fixture, eventData.elementSelector); element.triggerEventHandler(eventData.name, eventData.data); diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.html b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.html index 9ec99cebdafed843b5ded6af4b56c34fb2b04638..f0b1defde6fba19d8ef6144a37945c05dcbc3735 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.html +++ b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.html @@ -23,11 +23,6 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<ods-dropdown-menu-text-item - class="border-b border-b-grayborder border-t-grayborder bg-whitetext" - title="Benutzerleitfaden" - description="Alle Funktionen der Allgemeinen Fachanwendung (Alfa) erklärt." -> - <ods-file-icon icon fileType="pdf" size="large"></ods-file-icon> - <alfa-open-documentation-button additionalContent [url]="url" data-test-id="documentations-component" /> -</ods-dropdown-menu-text-item> +<ods-dropdown-menu-link-item caption="Benutzerleitfaden Alfa" text="PDF öffnen" [url]="url"> + <ods-file-icon icon fileType="pdf" size="medium" /> +</ods-dropdown-menu-link-item> diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.spec.ts b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.spec.ts index b7307c4a9f800117718ab72571c87289e011907c..fb232797a308c689f244460f103deafbabf9c5f3 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.spec.ts +++ b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.spec.ts @@ -22,10 +22,9 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { DropdownMenuTextItemComponent, FileIconComponent } from '@ods/system'; +import { DropdownMenuLinkItemComponent, FileIconComponent } from '@ods/system'; import { MockComponent } from 'ng-mocks'; import { DocumentationComponent } from './documentation.component'; -import { OpenDocumentationButtonComponent } from './open-documentation-button/open-documentation-button.component'; describe('DocumentationComponent', () => { let component: DocumentationComponent; @@ -33,12 +32,7 @@ describe('DocumentationComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - DocumentationComponent, - MockComponent(OpenDocumentationButtonComponent), - MockComponent(DropdownMenuTextItemComponent), - MockComponent(FileIconComponent), - ], + declarations: [DocumentationComponent, MockComponent(DropdownMenuLinkItemComponent), MockComponent(FileIconComponent)], }).compileComponents(); fixture = TestBed.createComponent(DocumentationComponent); diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.html b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.html deleted file mode 100644 index d3f460b90b5c7c7232d01e2b4e0674ddee2a8a8c..0000000000000000000000000000000000000000 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.html +++ /dev/null @@ -1,32 +0,0 @@ -<!-- - - Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<ozgcloud-open-url-button - text="Öffnen" - [url]="url" - [targetName]="'_blank'" - [tooltip]="'Öffnet in einem neuen Tab'" - data-test-id="open-documentation-button" -/> diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.spec.ts b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.spec.ts deleted file mode 100644 index a57801c4f80be45225f6d78c3789d1cdec31b271..0000000000000000000000000000000000000000 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { OpenDocumentationButtonComponent } from './open-documentation-button.component'; -import { MockComponent } from 'ng-mocks'; -import { OpenUrlButtonComponent } from '@alfa-client/ui'; - -describe('OpenDocumentationButtonComponent', () => { - let component: OpenDocumentationButtonComponent; - let fixture: ComponentFixture<OpenDocumentationButtonComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [OpenDocumentationButtonComponent, MockComponent(OpenUrlButtonComponent)], - }).compileComponents(); - - fixture = TestBed.createComponent(OpenDocumentationButtonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.ts b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.ts deleted file mode 100644 index c0719256cfb74979b4cbe95a7c3e3c451cd08af4..0000000000000000000000000000000000000000 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { Component, Input } from '@angular/core'; - -@Component({ - selector: 'alfa-open-documentation-button', - templateUrl: './open-documentation-button.component.html', - styleUrls: ['./open-documentation-button.component.scss'], -}) -export class OpenDocumentationButtonComponent { - @Input() url: string; -} diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html index 24ee1e914732e11ea00e3dad00a7cffea735b000..24583fe97f504dcb3d35279e5a1ab0d932a97ca9 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html +++ b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html @@ -38,6 +38,7 @@ } @if (apiRootStateResource?.resource?.impressumUrl) { + <div class="h-2"></div> <ods-dropdown-menu-link-item [url]="apiRootStateResource.resource.impressumUrl" text="Impressum" data-test-id="impressum" /> } </ods-dropdown-menu> diff --git a/alfa-client/libs/user-assistance/src/lib/user-assistance.module.ts b/alfa-client/libs/user-assistance/src/lib/user-assistance.module.ts index 63fc01ce61325ce4935a8292df7314a550e83940..f448be3a653b757112bf18d262ff617fcf3600ab 100644 --- a/alfa-client/libs/user-assistance/src/lib/user-assistance.module.ts +++ b/alfa-client/libs/user-assistance/src/lib/user-assistance.module.ts @@ -34,7 +34,6 @@ import { import { MatFabButton } from '@angular/material/button'; import { MatMenuTrigger } from '@angular/material/menu'; import { DocumentationComponent } from './help-menu/documentation/documentation.component'; -import { OpenDocumentationButtonComponent } from './help-menu/documentation/open-documentation-button/open-documentation-button.component'; import { HelpButtonComponent } from './help-menu/help-button/help-button.component'; import { HelpMenuComponent } from './help-menu/help-menu.component'; import { OpenUrlButtonComponent } from '@alfa-client/ui'; @@ -53,7 +52,7 @@ import { OpenUrlButtonComponent } from '@alfa-client/ui'; DropdownMenuLinkItemComponent, OpenUrlButtonComponent, ], - declarations: [HelpMenuComponent, DocumentationComponent, OpenDocumentationButtonComponent, HelpButtonComponent], + declarations: [HelpMenuComponent, DocumentationComponent, HelpButtonComponent], exports: [HelpMenuComponent], }) export class UserAssistanceModule {} diff --git a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html index a2aac078aaf9d5fc6783864da5b363cf460d9bee..797bf6c78e5323f1914b56376d75b5409c19b3c5 100644 --- a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html +++ b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html @@ -36,7 +36,7 @@ class="user-profile-icon" > </alfa-user-icon> - <ods-dropdown-menu-button-item caption="Abmelden" (itemClicked)="logoutEmitter.emit()" dataTestId="logout-button"> - <ods-logout-icon icon /> + <ods-dropdown-menu-button-item caption="Abmelden" (clickEmitter)="logoutEmitter.emit()" dataTestId="logout-button"> + <ods-logout-icon icon class="fill-primary" /> </ods-dropdown-menu-button-item> </ods-dropdown-menu> diff --git a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.spec.ts b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.spec.ts index ab53c008163ee44d5f785b065da91de6f203363c..2bf68369cdab87155caf1bb0d47489feb1552fc7 100644 --- a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.spec.ts +++ b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.spec.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { createStateResource } from '@alfa-client/tech-shared'; -import { dispatchEventFromFixture, getElementComponentFromFixtureByCss, mock, useFromMock } from '@alfa-client/test-utils'; +import { dispatchEventFromFixture, getElementComponentFromFixtureByCss, mock, MockEvent, useFromMock, } from '@alfa-client/test-utils'; import { UserIconComponent } from '@alfa-client/user-profile'; import { getUserName, UserProfileResource } from '@alfa-client/user-profile-shared'; import { EventEmitter } from '@angular/core'; @@ -90,7 +90,7 @@ describe('UserProfileInHeaderComponent', () => { describe('template', () => { describe('click on logout button', () => { it('should emit logout event', () => { - dispatchEventFromFixture(fixture, logoutButton, 'itemClicked'); + dispatchEventFromFixture(fixture, logoutButton, MockEvent.CLICK); expect(component.logoutEmitter.emit).toHaveBeenCalled(); }); diff --git a/alfa-client/tsconfig.base.json b/alfa-client/tsconfig.base.json index f6bb4c8b2657d8acef026b997d32794971be5656..615131502b18ad4b0d693d9d2baeed734f62c4ce 100644 --- a/alfa-client/tsconfig.base.json +++ b/alfa-client/tsconfig.base.json @@ -78,7 +78,8 @@ "@alfa-client/zustaendige-stelle-shared": ["libs/zustaendige-stelle-shared/src/index.ts"], "@authentication": ["libs/authentication/src/index.ts"], "@ods/component": ["libs/design-component/src/index.ts"], - "@ods/system": ["libs/design-system/src/index.ts"] + "@ods/system": ["libs/design-system/src/index.ts"], + "admin-user-profile": ["libs/admin/admin-user-profile/src/index.ts"] } }, "exclude": ["node_modules", "tmp"]