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/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 index 80d2884a5bf5889a1a14a1b29970031b7f571144..19852a52775d1f104314441a3a85ce466a7df7f0 100644 --- 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 @@ -21,19 +21,28 @@ * 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(); - const documentationLink: string = 'http://dummy-leitfaden.url'; + 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', () => { 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 c88d54bf3dac14f0f0a7b5f124a66e7d1e26d60f..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.clickOrganisationsEinheitenNavigationItem(); - - 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 2b8eaeb70ab47d7845d24f973bac43f7645c25b2..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.clickOrganisationsEinheitenNavigationItem(); - 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/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/apps/admin-e2e/src/support/linkrels.ts b/alfa-client/apps/admin-e2e/src/support/linkrels.ts new file mode 100644 index 0000000000000000000000000000000000000000..4891aa61e1bdf2e6bcc77f167e7aa6a7fd7aedb1 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/support/linkrels.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ +export enum ApiRootLinkRelE2E { + DOCUMENTATIONS = 'documentations', +} 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 dd680a850d96b5fb67f292c86e5e41f31207e094..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 @@ -63,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..5ad4b5ec7838822bde049dad75347be1d6943d52 --- /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,50 @@ +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(console.info), + 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/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 4bb01a272a95332484183637cc71de58207548e3..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,17 +77,27 @@ describe('OpenDialogButtonComponent', () => { }); it('should call dialog service to open dialog', () => { + component.dialogData = dummyDialogData; + dispatchEventFromFixture(fixture, openDialog, MockEvent.CLICK); - expect(dialogService.openInContext).toHaveBeenCalledWith(componentRef.instance.constructor, viewContainerRef); + 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 66f08331d70aec3b84830d1650d38a4011d912f8..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,5 +1,6 @@ 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, inject, Injector, Input, ViewContainerRef } from '@angular/core'; import { ButtonComponent, ButtonVariants } from '@ods/system'; @@ -8,11 +9,13 @@ import { ButtonComponent, ButtonVariants } from '@ods/system'; 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> @@ -31,13 +34,18 @@ export class OpenDialogButtonComponent { @Input() label: string; @Input() dataTestId: string; + @Input() dataTestClass: string; @Input() variant: ButtonVariants['variant'] = 'primary'; + @Input() dialogData: any; + @Input() size: ButtonVariants['size']; public open(): void { - this.dialogService.openInContext(this._createComponent().instance.constructor, this.viewContainerRef); + 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/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; +}