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 474b719fef02f95377505bb0d34c85df693b46ae..5a67a4226ff7d7d4eda4f7d57a69fdca18bd4d72 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 @@ -1,8 +1,18 @@ +import 'cypress-real-events'; import { exist } from '../../support/cypress.util'; export class BenutzerE2EComponent { - private readonly benutzerHinzufuegenButton: string = 'add-user-button'; - private readonly userEntry: string = 'user-entry-'; + private readonly benutzerHinzufuegenButton: string = 'Add-user-button'; + private readonly userEntry: string = 'User-entry-'; + private readonly userVorname: string = 'Vorname-text-input'; + private readonly userNachname: string = 'Nachname-text-input'; + private readonly userBenutzername: string = 'Benutzername-text-input'; + private readonly userMail: string = 'E-Mail-text-input'; + private readonly addOEButton: string = 'Add-organisationseinheit-button'; + private readonly adminCheckbox: string = 'Admin-checkbox-editor'; + private readonly loeschenCheckbox: string = 'Loschen-checkbox-editor'; + private readonly userCheckbox: string = 'User-checkbox-editor'; + private readonly postCheckbox: string = 'Poststelle-checkbox-editor'; public getHinzufuegenButton(): Cypress.Chainable<Element> { return cy.getTestElement(this.benutzerHinzufuegenButton); @@ -22,4 +32,60 @@ export class BenutzerE2EComponent { exist(cy.contains(phrase)); }); } + + public getVornameInput(): Cypress.Chainable<Element> { + return cy.getTestElement(this.userVorname); + } + + public getNachnameInput(): Cypress.Chainable<Element> { + return cy.getTestElement(this.userNachname); + } + + public getBenutzernameInput(): Cypress.Chainable<Element> { + return cy.getTestElement(this.userBenutzername); + } + + public getMailInput(): Cypress.Chainable<Element> { + return cy.getTestElement(this.userMail); + } + + public getOEButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.addOEButton); + } + + public addOrganisationseinheit(): void { + this.getOEButton().click(); + } + + public getAdminCheckbox(): Cypress.Chainable<Element> { + return cy.getTestElement(this.adminCheckbox); + } + + public clickAdminCheckbox(): void { + this.getAdminCheckbox().click(); + } + + public getLoeschenCheckbox() { + return cy.getTestElement(this.loeschenCheckbox); + } + + public clickLoeschenCheckbox(): void { + this.getLoeschenCheckbox().click(); + } + + public getUserCheckbox(): Cypress.Chainable<Element> { + return cy.getTestElement(this.userCheckbox); + } + + public clickUserCheckbox(): void { + this.getUserCheckbox().click(); + } + + public getPostCheckbox(): Cypress.Chainable<Element> { + return cy.getTestElement(this.postCheckbox); + } + + public clickPostCheckbox(): void { + this.getPostCheckbox().click(); + } } diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer_rollen.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer_rollen.cy.ts index d1c52395edf3286ee593f72c2f5cc5c752a7de4b..df8ad2a80810e40567c1788ce9d2af63ed96e912 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer_rollen.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer_rollen.cy.ts @@ -1,7 +1,7 @@ -import { MainPage } from 'apps/admin-e2e/src/page-objects/main.po'; -import { exist } from 'apps/admin-e2e/src/support/cypress.util'; -import { loginAsAriane } from 'apps/admin-e2e/src/support/user-util'; -import { BenutzerE2EComponent } from 'apps/admin-e2e/src/components/benutzer/benutzer.e2e.component'; +import { BenutzerE2EComponent } from '../../components/benutzer/benutzer.e2e.component'; +import { MainPage } from '../../page-objects/main.po'; +import { beChecked, beEnabled, exist, notBeChecked, notBeEnabled } from '../../support/cypress.util'; +import { loginAsAriane } from '../../support/user-util'; const mainPage: MainPage = new MainPage(); const benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); @@ -9,7 +9,7 @@ const role1: string = 'VERWALTUNG_USER'; const role2: string = 'VERWALTUNG_LOESCHEN'; const role3: string = 'VERWALTUNG_POSTSTELLE'; const orga1: string = 'Ordnungsamt'; -const orga2: string = 'Landesamt für Denkmalpflege'; +const orga2: string = 'Denkmalpflege'; const orga3: string = 'Wirtschaftsförderung'; const orga_none: string = 'keine zuständige Stelle zugewiesen'; const mail1: string = 'peter.von.der.post@ozg-sh.de'; @@ -37,4 +37,65 @@ describe('Benutzer und Rollen', () => { benutzerPage.stringExistsInUserEntry(mail1, 'peter'); benutzerPage.stringExistsInUserEntry(role3, 'peter'); }); + + it('should show single user screen on click', () => { + benutzerPage.addUser(); + + exist(benutzerPage.getVornameInput()); + exist(benutzerPage.getNachnameInput()); + exist(benutzerPage.getBenutzernameInput()); + exist(benutzerPage.getMailInput()); + + notBeChecked(benutzerPage.getAdminCheckbox()); + notBeChecked(benutzerPage.getLoeschenCheckbox()); + notBeChecked(benutzerPage.getUserCheckbox()); + notBeChecked(benutzerPage.getPostCheckbox()); + }); + + it('should activate loeschen checkbox and deactivate the other two checkboxes', () => { + benutzerPage.clickLoeschenCheckbox(); + beChecked(benutzerPage.getLoeschenCheckbox()); + notBeEnabled(benutzerPage.getUserCheckbox()); + notBeEnabled(benutzerPage.getPostCheckbox()); + + benutzerPage.clickLoeschenCheckbox(); + notBeChecked(benutzerPage.getLoeschenCheckbox()); + beEnabled(benutzerPage.getUserCheckbox()); + beEnabled(benutzerPage.getPostCheckbox()); + }); + + it('should additionally activate and deactivate admin checkbox', () => { + benutzerPage.clickLoeschenCheckbox(); + benutzerPage.clickAdminCheckbox(); + beChecked(benutzerPage.getLoeschenCheckbox()); + beChecked(benutzerPage.getAdminCheckbox()); + + benutzerPage.clickAdminCheckbox(); + notBeChecked(benutzerPage.getAdminCheckbox()); + }); + + it('should activate user checkbox and deactivate the other two checkboxes', () => { + benutzerPage.clickLoeschenCheckbox(); + benutzerPage.clickUserCheckbox(); + beChecked(benutzerPage.getUserCheckbox()); + notBeEnabled(benutzerPage.getLoeschenCheckbox()); + notBeEnabled(benutzerPage.getPostCheckbox()); + + benutzerPage.clickUserCheckbox(); + notBeChecked(benutzerPage.getUserCheckbox()); + beEnabled(benutzerPage.getLoeschenCheckbox()); + beEnabled(benutzerPage.getPostCheckbox()); + }); + + it('should activate post checkbox and deactivate the other two checkboxes', () => { + benutzerPage.clickPostCheckbox(); + beChecked(benutzerPage.getPostCheckbox()); + notBeEnabled(benutzerPage.getLoeschenCheckbox()); + notBeEnabled(benutzerPage.getUserCheckbox()); + + benutzerPage.clickPostCheckbox(); + notBeChecked(benutzerPage.getPostCheckbox()); + beEnabled(benutzerPage.getLoeschenCheckbox()); + beEnabled(benutzerPage.getUserCheckbox()); + }); }); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/postfach/signatur.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/postfach/signatur.cy.ts index 884093ac7af1a52e3485bd8133a431515fa3f095..32323b02826811f53d7c617bf4558f870a1d29b4 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/postfach/signatur.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/postfach/signatur.cy.ts @@ -15,7 +15,7 @@ describe('Signatur', () => { loginAsAriane(); }); - it('should clear current signature0', () => { + it('should clear current signature', () => { waitForSpinnerToDisappear(); exist(postfachTab.getSignaturText()); diff --git a/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts index 14c867e8b121a71674c1ef602bf52b8a5234ed31..0d49928d6e5f08c470ff6489717098322ef970ef 100644 --- a/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts +++ b/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts @@ -4,8 +4,9 @@ import { HeaderE2EComponent } from './header.po'; export class MainPage { private readonly buildInfo: BuildInfoE2EComponent = new BuildInfoE2EComponent(); private readonly header: HeaderE2EComponent = new HeaderE2EComponent(); - private readonly benutzerTab: string = 'nav-item-Benutzer__Rollen'; - private readonly postfachTab: string = 'nav-item-Postfach'; + private readonly benutzerTab: string = 'caption-Benutzer__Rollen'; + private readonly postfachTab: string = 'caption-Postfach'; + private readonly organisationseinheitenTab: string = 'caption-Organisationseinheiten'; public getBuildInfo(): BuildInfoE2EComponent { return this.buildInfo; @@ -22,6 +23,14 @@ export class MainPage { public clickBenutzerTab(): void { this.getBenutzerTab().click(); } + + public getOrganisationseinheitenTab(): Cypress.Chainable<Element> { + return cy.getTestElement(this.organisationseinheitenTab); + } + + public clickOrganisationseinheitenTab(): void { + this.getOrganisationseinheitenTab().click(); + } } export function waitForSpinnerToDisappear(): boolean { diff --git a/alfa-client/apps/admin-e2e/src/support/cypress.util.ts b/alfa-client/apps/admin-e2e/src/support/cypress.util.ts index 9b68b3206ff29d0c3eab2ea3c9f292b4d776030d..4b45f922abb7e64be5329d03112af9769196c9fa 100644 --- a/alfa-client/apps/admin-e2e/src/support/cypress.util.ts +++ b/alfa-client/apps/admin-e2e/src/support/cypress.util.ts @@ -2,104 +2,104 @@ import { wait } from './cypress-helper'; //TODO Naming der Methoden geradeziehen -export function containClass(element: any, cssClass: string): void { +export function containClass(element: Cypress.Chainable<Element>, cssClass: string): void { element.should('have.class', cssClass); } -export function notContainClass(element: any, cssClass: string): void { +export function notContainClass(element: Cypress.Chainable<Element>, cssClass: string): void { element.should('not.have.class', cssClass); } -export function exist(element: any): void { +export function exist(element: Cypress.Chainable<Element>): void { element.should('exist'); } -export function notExist(element: any): void { +export function notExist(element: Cypress.Chainable<Element>): void { element.should('not.exist'); } -export function haveText(element: any, text: string): void { +export function haveText(element: Cypress.Chainable<Element>, text: string): void { element .invoke('text') .then((elementText) => elementText.trim()) .should('equal', text); } -export function haveValue(element: any, value: string): void { +export function haveValue(element: Cypress.Chainable<Element>, value: string): void { element.should('have.value', value); } -export function haveFocus(element: any): void { +export function haveFocus(element: Cypress.Chainable<Element>): void { element.should('have.focus'); } -export function mouseEnter(element: any): void { +export function mouseEnter(element: Cypress.Chainable<Element>): void { element.trigger('mouseenter'); } -export function mouseOver(element: any): void { +export function mouseOver(element: Cypress.Chainable<Element>): void { element.trigger('mouseover'); } -export function contains(element: any, containing: string): void { +export function contains(element: Cypress.Chainable<Element>, containing: string): void { element.should('exist').contains(containing); } -export function notContains(element: any, containing: string): void { +export function notContains(element: Cypress.Chainable<Element>, containing: string): void { element.contains(containing).should('not.exist'); } -export function haveLength(element: any, length: number): void { +export function haveLength(element: Cypress.Chainable<Element>, length: number): void { element.should('have.length', length); } -export function beChecked(element: any): void { +export function beChecked(element: Cypress.Chainable<Element>): void { element.should('be.checked'); } -export function notBeChecked(element: any): void { +export function notBeChecked(element: Cypress.Chainable<Element>): void { element.should('not.be.checked'); } +export function beEnabled(element: Cypress.Chainable<Element>): void { + element.should('be.enabled'); +} + +export function notBeEnabled(element: Cypress.Chainable<Element>): void { + element.should('not.be.enabled'); +} + //TODO: "first()" rausnehmen -> im html eine entprechende data-test-id ansprechen?! | trennen in "get" und "verify" -export function shouldFirstContains(element: any, containing: string) { +export function shouldFirstContains(element: Cypress.Chainable<Element>, containing: string) { element.first().should('exist').contains(containing); } -export function shouldHaveAttributeBeGreaterThan( - element: any, - attributeName: string, - value: number, -) { +export function shouldHaveAttributeBeGreaterThan(element: Cypress.Chainable<Element>, attributeName: string, value: number) { element.first().should('exist').invoke(attributeName).should('be.gt', value); } -export function shouldHaveAttributeBeLowerThan(element: any, attributeName: string, value: number) { +export function shouldHaveAttributeBeLowerThan(element: Cypress.Chainable<Element>, attributeName: string, value: number) { element.first().should('exist').invoke(attributeName).should('be.gt', value); } // -export function shouldHaveAttribute(element: any, name: string, value: string) { +export function shouldHaveAttribute(element: Cypress.Chainable<Element>, name: string, value: string) { element.should('have.attr', name, value); } -export function visible(element: any) { +export function visible(element: Cypress.Chainable<Element>) { element.should('be.visible'); } -export function notBeVisible(element: any) { +export function notBeVisible(element: Cypress.Chainable<Element>) { element.should('not.be.visible'); } -export function enter(element: any): void { +export function enter(element: Cypress.Chainable<Element>): void { element.clear().type(CypressKeyboardActions.ENTER); } -export function enterWith( - element: Cypress.Chainable<JQuery<HTMLElement>>, - value: string, - delayBeforeEnter: number = 200, -): void { +export function enterWith(element: Cypress.Chainable<JQuery<HTMLElement>>, value: string, delayBeforeEnter: number = 200): void { element.clear().type(value); wait(delayBeforeEnter); element.type(CypressKeyboardActions.ENTER); @@ -109,7 +109,7 @@ export function typeText(element: Cypress.Chainable<JQuery<HTMLElement>>, value: element.type(value); } -export function backspaceOn(element: any): void { +export function backspaceOn(element: Cypress.Chainable<Element>): void { element.type(CypressKeyboardActions.BACKSPACE); } 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 ea31dd492f621f9e87b99f9ebee7d26f26014677..547e09233158c2f9343e0550a9345ff97ac3dab5 100644 --- a/alfa-client/apps/admin/src/app/app.component.spec.ts +++ b/alfa-client/apps/admin/src/app/app.component.spec.ts @@ -10,7 +10,7 @@ import { notExistsAsHtmlElement, } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Router, RouterOutlet } from '@angular/router'; +import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { AdminLogoIconComponent, MailboxIconComponent, @@ -45,6 +45,18 @@ describe('AppComponent', () => { }; const router: Mock<Router> = mock(Router); + const route: Mock<ActivatedRoute> = { + ...mock(ActivatedRoute), + snapshot: { + queryParams: { + iss: 'some-iss', + state: 'some-state', + session_state: 'some-session-state', + code: 'some-code', + }, + } as any, + }; + const apiRootService: Mock<ApiRootService> = mock(ApiRootService); beforeEach(async () => { @@ -76,6 +88,10 @@ describe('AppComponent', () => { provide: Router, useValue: router, }, + { + provide: ActivatedRoute, + useValue: route, + }, ], }).compileComponents(); }); @@ -85,26 +101,28 @@ describe('AppComponent', () => { component = fixture.componentInstance; }); - it(`should have as title 'admin'`, () => { - const appTitle: string = fixture.componentInstance.title; + describe('component', () => { + it(`should have as title 'admin'`, () => { + const appTitle: string = fixture.componentInstance.title; - expect(appTitle).toEqual('admin'); - }); + expect(appTitle).toEqual('admin'); + }); - describe('ngOnInit', () => { - it('should call authService login', () => { - component.ngOnInit(); + describe('ngOnInit', () => { + it('should call authService login', () => { + component.ngOnInit(); - expect(authenticationService.login).toHaveBeenCalled(); - }); + expect(authenticationService.login).toHaveBeenCalled(); + }); - it('should call doAfterLoggedIn', async () => { - component.doAfterLoggedIn = jest.fn(); + it('should call doAfterLoggedIn', async () => { + component.doAfterLoggedIn = jest.fn(); - component.ngOnInit(); - await fixture.whenStable(); + component.ngOnInit(); + await fixture.whenStable(); - expect(component.doAfterLoggedIn).toHaveBeenCalled(); + expect(component.doAfterLoggedIn).toHaveBeenCalled(); + }); }); describe('do after logged in', () => { @@ -114,98 +132,109 @@ describe('AppComponent', () => { expect(apiRootService.getApiRoot).toHaveBeenCalled(); }); - it('should navigate to default route', () => { + it('should call forwardWithoutAuthenticationParams', () => { + component.forwardWithoutAuthenticationParams = jest.fn(); + component.doAfterLoggedIn(); - expect(router.navigate).toHaveBeenCalledWith(['/']); + expect(component.forwardWithoutAuthenticationParams).toHaveBeenCalled(); }); }); - }); - it('show not show header if apiRoot is not loaded', () => { - component.apiRootStateResource$ = of(createEmptyStateResource<ApiRootResource>()); + describe('forward without authentication params', () => { + it('should navigate to same route without authentication params', () => { + component.forwardWithoutAuthenticationParams(); - notExistsAsHtmlElement(fixture, adminHeaderSelector); + expect(router.navigate).toHaveBeenCalledWith([], { queryParams: {} }); + }); + }); }); - describe('user profile button', () => { - beforeEach(() => { - component.apiRootStateResource$ = of(createStateResource(createApiRootResource())); + describe('template', () => { + it('show not show header if apiRoot is not loaded', () => { + component.apiRootStateResource$ = of(createEmptyStateResource<ApiRootResource>()); + + notExistsAsHtmlElement(fixture, adminHeaderSelector); }); - it('should show if apiRoot exists', () => { - fixture.detectChanges(); + describe('user profile button', () => { + beforeEach(() => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource())); + }); + + it('should show if apiRoot exists', () => { + fixture.detectChanges(); - existsAsHtmlElement(fixture, userProfileButtonSelector); + existsAsHtmlElement(fixture, userProfileButtonSelector); + }); }); - }); - describe('administration logo', () => { - const apiResource: ApiRootResource = createApiRootResource(); + describe('administration logo', () => { + const apiResource: ApiRootResource = createApiRootResource(); - beforeEach(() => { - component.apiRootStateResource$ = of(createStateResource(apiResource)); - fixture.detectChanges(); - }); + beforeEach(() => { + component.apiRootStateResource$ = of(createStateResource(apiResource)); + fixture.detectChanges(); + }); - it('should navigate to start page on click', () => { - dispatchEventFromFixture(fixture, logoLink, 'click'); + it('should navigate to start page on click', () => { + dispatchEventFromFixture(fixture, logoLink, 'click'); - expect(router.navigate).toHaveBeenCalledWith(['/']); + expect(router.navigate).toHaveBeenCalledWith([], { queryParams: {} }); + }); }); - }); - describe('navigation', () => { - beforeEach(() => {}); - it('should show links if configuration link exists', () => { - component.apiRootStateResource$ = of(createStateResource(createApiRootResource([ApiRootLinkRel.CONFIGURATION]))); - fixture.detectChanges(); + describe('navigation', () => { + it('should show links if configuration link exists', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource([ApiRootLinkRel.CONFIGURATION]))); + fixture.detectChanges(); - const navbarElement: HTMLElement = getElementFromFixture(fixture, navigationSelector); + const navbarElement: HTMLElement = getElementFromFixture(fixture, navigationSelector); - expect(navbarElement.children.length).toBeGreaterThan(0); - }); + expect(navbarElement.children.length).toBeGreaterThan(0); + }); - it('should not not show links if configuration resource not available', () => { - component.apiRootStateResource$ = of(createStateResource(createApiRootResource([]))); - fixture.detectChanges(); + it('should not not show links if configuration resource not available', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource([]))); + fixture.detectChanges(); - const navbarElement: HTMLElement = getElementFromFixture(fixture, navigationSelector); + const navbarElement: HTMLElement = getElementFromFixture(fixture, navigationSelector); - expect(navbarElement.children.length).toBe(0); + expect(navbarElement.children.length).toBe(0); + }); }); - }); - describe('build version', () => { - it('should not be rendered if api root not loaded', () => { - notExistsAsHtmlElement(fixture, buildInfoSelector); - }); + describe('build version', () => { + it('should not be rendered if api root not loaded', () => { + notExistsAsHtmlElement(fixture, buildInfoSelector); + }); - it('should show after apiRoot loaded', () => { - component.apiRootStateResource$ = of(createStateResource(createApiRootResource())); - fixture.detectChanges(); + it('should show after apiRoot loaded', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource())); + fixture.detectChanges(); - existsAsHtmlElement(fixture, buildInfoSelector); + existsAsHtmlElement(fixture, buildInfoSelector); + }); }); - }); - describe('router outlet', () => { - beforeEach(() => {}); + describe('router outlet', () => { + beforeEach(() => {}); - it('should exist if configuration resource available', () => { - component.apiRootStateResource$ = of(createStateResource(createApiRootResource([ApiRootLinkRel.CONFIGURATION]))); + it('should exist if configuration resource available', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource([ApiRootLinkRel.CONFIGURATION]))); - fixture.detectChanges(); + fixture.detectChanges(); - existsAsHtmlElement(fixture, routerOutletSelector); - }); + existsAsHtmlElement(fixture, routerOutletSelector); + }); - it('should not exist if configuration resource not available', () => { - component.apiRootStateResource$ = of(createStateResource(createApiRootResource())); + it('should not exist if configuration resource not available', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource())); - fixture.detectChanges(); + fixture.detectChanges(); - notExistsAsHtmlElement(fixture, routerOutletSelector); + notExistsAsHtmlElement(fixture, routerOutletSelector); + }); }); }); }); diff --git a/alfa-client/apps/admin/src/app/app.component.ts b/alfa-client/apps/admin/src/app/app.component.ts index bccefbebc1e14ca084e18329ba7f604e28434731..e1d4c6d2a424c489756ec2966dde744126e360fa 100644 --- a/alfa-client/apps/admin/src/app/app.component.ts +++ b/alfa-client/apps/admin/src/app/app.component.ts @@ -1,7 +1,7 @@ import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { AuthenticationService } from 'libs/authentication/src/lib/authentication.service'; import { Observable } from 'rxjs'; @@ -19,6 +19,7 @@ export class AppComponent implements OnInit { public authenticationService: AuthenticationService, private apiRootService: ApiRootService, private router: Router, + private route: ActivatedRoute, ) {} ngOnInit(): void { @@ -27,7 +28,17 @@ export class AppComponent implements OnInit { doAfterLoggedIn(): void { this.apiRootStateResource$ = this.apiRootService.getApiRoot(); - this.router.navigate(['/']); + this.forwardWithoutAuthenticationParams(); + } + + forwardWithoutAuthenticationParams() { + const queryParams = this.getQueryParamsWithoutAuthentication(); + this.router.navigate([], { queryParams }); + } + + private getQueryParamsWithoutAuthentication(): Params { + const { iss, state, session_state, code, ...queryParams } = this.route.snapshot.queryParams; + return queryParams; } protected readonly ApiRootLinkRel = ApiRootLinkRel; diff --git a/alfa-client/apps/admin/src/app/app.module.ts b/alfa-client/apps/admin/src/app/app.module.ts index 8758a051d1c6f6ef28a9ec6be4620cb3eb3dec5e..2b32d7c65df119f3c0b6469b6d7fa3d8cd43d259 100644 --- a/alfa-client/apps/admin/src/app/app.module.ts +++ b/alfa-client/apps/admin/src/app/app.module.ts @@ -34,6 +34,7 @@ import { OrganisationsEinheitFormPageComponent } from '../pages/organisationsein import { OrganisationsEinheitPageComponent } from '../pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component'; import { PostfachPageComponent } from '../pages/postfach/postfach-page/postfach-page.component'; import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component'; +import { UserAddPageComponent } from '../pages/users-roles/user-add-page/user-add-page.component'; import { UserRolesPageComponent } from '../pages/users-roles/user-roles-page/user-roles-page.component'; import { AppComponent } from './app.component'; import { appRoutes } from './app.routes'; @@ -49,6 +50,7 @@ registerLocaleData(localeDe); OrganisationsEinheitFormPageComponent, UserProfileButtonContainerComponent, UnavailablePageComponent, + UserAddPageComponent, ], imports: [ CommonModule, diff --git a/alfa-client/apps/admin/src/app/app.routes.ts b/alfa-client/apps/admin/src/app/app.routes.ts index 4225458cdaeb73330edbafa34f76c46209ebfc27..6f9acc7ca367f1914ae7d4ff21e72c5004316463 100644 --- a/alfa-client/apps/admin/src/app/app.routes.ts +++ b/alfa-client/apps/admin/src/app/app.routes.ts @@ -1,32 +1,39 @@ import { Route } from '@angular/router'; +import { ROUTES } from 'libs/admin/shared'; import { OrganisationsEinheitFormPageComponent } from '../pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component'; import { OrganisationsEinheitPageComponent } from '../pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component'; import { PostfachPageComponent } from '../pages/postfach/postfach-page/postfach-page.component'; +import { UserAddPageComponent } from '../pages/users-roles/user-add-page/user-add-page.component'; import { UserRolesPageComponent } from '../pages/users-roles/user-roles-page/user-roles-page.component'; export const appRoutes: Route[] = [ { path: '', - redirectTo: 'postfach', + redirectTo: ROUTES.POSTFACH, pathMatch: 'full', }, { - path: 'postfach', + path: ROUTES.POSTFACH, component: PostfachPageComponent, title: 'Admin | Postfach', }, { - path: 'benutzer_und_rollen', + path: ROUTES.BENUTZER_UND_ROLLEN, component: UserRolesPageComponent, title: 'Admin | Benutzer & Rollen', }, { - path: 'organisationseinheiten', + path: ROUTES.BENUTZER_UND_ROLLEN_NEU, + component: UserAddPageComponent, + title: 'Admin | Benutzer anlegen', + }, + { + path: ROUTES.ORGANISATIONSEINHEITEN, component: OrganisationsEinheitPageComponent, title: 'Admin | Organisationseinheiten', }, { - path: 'organisationseinheiten/:organisationsEinheitUrl', + path: `${ROUTES.ORGANISATIONSEINHEITEN}/:organisationsEinheitUrl`, component: OrganisationsEinheitFormPageComponent, title: 'Admin | Organisationseinheit', }, diff --git a/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.html b/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.html new file mode 100644 index 0000000000000000000000000000000000000000..dad58a8662dd19eb059273474e2df1474305d3b4 --- /dev/null +++ b/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.html @@ -0,0 +1 @@ +<admin-user-add-form /> diff --git a/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.spec.ts b/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..eecaa8e5c0c24be917a8b968d2af926dbf1ff56f --- /dev/null +++ b/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.spec.ts @@ -0,0 +1,23 @@ +import { UserAddFormComponent } from '@admin-client/admin-settings'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockComponent } from 'ng-mocks'; +import { UserAddPageComponent } from './user-add-page.component'; + +describe('UserAddPageComponent', () => { + let component: UserAddPageComponent; + let fixture: ComponentFixture<UserAddPageComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [UserAddPageComponent, MockComponent(UserAddFormComponent)], + }).compileComponents(); + + fixture = TestBed.createComponent(UserAddPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.ts b/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3bcc1eaf5f74df921a17932bea865ce96a61907 --- /dev/null +++ b/alfa-client/apps/admin/src/pages/users-roles/user-add-page/user-add-page.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'user-add-page', + templateUrl: './user-add-page.component.html', +}) +export class UserAddPageComponent {} diff --git a/alfa-client/libs/admin/settings/src/index.ts b/alfa-client/libs/admin/settings/src/index.ts index a13f84185d2480694cbb145fb300e553cc668381..b1a8f83f9738553500db49b8058e577390482e95 100644 --- a/alfa-client/libs/admin/settings/src/index.ts +++ b/alfa-client/libs/admin/settings/src/index.ts @@ -3,5 +3,5 @@ export * from './lib/organisationseinheit/organisations-einheit.model'; export * from './lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component'; export * from './lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component'; export * from './lib/postfach/postfach-container/postfach-container.component'; -export * from './lib/shared/navigation-item/navigation-item.component'; +export * from './lib/users-roles/user-add-form/user-add-form.component'; export * from './lib/users-roles/users-roles.component'; diff --git a/alfa-client/libs/admin/settings/src/lib/admin-settings.module.ts b/alfa-client/libs/admin/settings/src/lib/admin-settings.module.ts index ad13c6426adbf09d39909c175b215c4e79f6c932..4e3f5c7bfe4c0a78873f7b92e3a6271a1668675f 100644 --- a/alfa-client/libs/admin/settings/src/lib/admin-settings.module.ts +++ b/alfa-client/libs/admin/settings/src/lib/admin-settings.module.ts @@ -8,7 +8,12 @@ import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import KcAdminClient from '@keycloak/keycloak-admin-client'; -import { ButtonWithSpinnerComponent, TextareaEditorComponent } from '@ods/component'; +import { + ButtonWithSpinnerComponent, + CheckboxEditorComponent, + TextareaEditorComponent, + TextEditorComponent, +} from '@ods/component'; import { ExclamationIconComponent, ListComponent, @@ -39,14 +44,8 @@ import { PostfachFormComponent } from './postfach/postfach-container/postfach-fo import { PostfachSignaturComponent } from './postfach/postfach-container/postfach-form/postfach-signatur/postfach-signatur.component'; import { createPostfachResourceService, PostfachResourceService } from './postfach/postfach-resource.service'; import { PostfachService } from './postfach/postfach.service'; -import { MoreItemButtonComponent } from './shared/more-menu/more-item-button/more-item-button.component'; -import { MoreMenuComponent } from './shared/more-menu/more-menu.component'; -import { NavigationItemComponent } from './shared/navigation-item/navigation-item.component'; -import { PrimaryButtonComponent } from './shared/primary-button/primary-button.component'; -import { SecondaryButtonComponent } from './shared/secondary-button/secondary-button.component'; -import { SpinnerComponent } from './shared/spinner/spinner.component'; -import { TextFieldComponent } from './shared/text-field/text-field.component'; import { ToUserNamePipe } from './user/to-user-name.pipe'; +import { UserAddFormComponent } from './users-roles/user-add-form/user-add-form.component'; import { UsersRolesComponent } from './users-roles/users-roles.component'; @NgModule({ @@ -54,19 +53,13 @@ import { UsersRolesComponent } from './users-roles/users-roles.component'; PostfachContainerComponent, PostfachFormComponent, PostfachSignaturComponent, - NavigationItemComponent, - TextFieldComponent, OrganisationsEinheitContainerComponent, OrganisationsEinheitListComponent, OrganisationsEinheitFormContainerComponent, OrganisationsEinheitFormComponent, OrganisationsEinheitSignaturComponent, - PrimaryButtonComponent, - SecondaryButtonComponent, - MoreMenuComponent, - MoreItemButtonComponent, - SpinnerComponent, UsersRolesComponent, + UserAddFormComponent, ], imports: [ CommonModule, @@ -74,6 +67,7 @@ import { UsersRolesComponent } from './users-roles/users-roles.component'; RouterModule, ReactiveFormsModule, TextInputComponent, + CheckboxEditorComponent, ButtonWithSpinnerComponent, TextareaEditorComponent, MailboxIconComponent, @@ -84,13 +78,14 @@ import { UsersRolesComponent } from './users-roles/users-roles.component'; ExclamationIconComponent, UiModule, NavigationSharedModule, + TextEditorComponent, ], exports: [ PostfachContainerComponent, OrganisationsEinheitContainerComponent, OrganisationsEinheitFormContainerComponent, - NavigationItemComponent, UsersRolesComponent, + UserAddFormComponent, ], providers: [ ConfigurationService, diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts index ad4f7bd11d40c6444ba2fdbbc169cd0b81aff0f4..86d331e417dd25431571427af73a542672943420 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts @@ -9,7 +9,6 @@ import { MockComponent } from 'ng-mocks'; import { of } from 'rxjs'; import { createInvalidParam, createProblemDetail } from '../../../../../../../tech-shared/test/error'; import { createAdminOrganisationsEinheitResource } from '../../../../../test/organisations-einheit/organisations-einheit'; -import { TextFieldComponent } from '../../../shared/text-field/text-field.component'; import { OrganisationsEinheitService } from '../../organisationseinheit.service'; import { OrganisationsEinheitFormComponent } from './organisationseinheit-form.component'; import { OrganisationsEinheitSignaturComponent } from './organisationseinheit-signatur/organisationseinheit-signatur.component'; @@ -31,7 +30,6 @@ describe('OrganisationsEinheitFormComponent', () => { await TestBed.configureTestingModule({ declarations: [ OrganisationsEinheitFormComponent, - MockComponent(TextFieldComponent), MockComponent(OrganisationsEinheitSignaturComponent), MockComponent(ButtonWithSpinnerComponent), ], diff --git a/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts index 8c8096d7df647918130510a8ac0fad54499064f7..119bf30d3ed7cac949543b020412c62a50e079f5 100644 --- a/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts +++ b/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts @@ -9,7 +9,6 @@ import { singleCold } from 'libs/tech-shared/test/marbles'; import { MockComponent } from 'ng-mocks'; import { EMPTY } from 'rxjs'; import { createPostfachResource } from '../../../../../test/postfach/postfach'; -import { TextFieldComponent } from '../../../shared/text-field/text-field.component'; import { PostfachResource } from '../../postfach.model'; import { PostfachService } from '../../postfach.service'; import { PostfachFormComponent } from './postfach-form.component'; @@ -28,12 +27,7 @@ describe('PostfachFormComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - PostfachFormComponent, - MockComponent(TextFieldComponent), - MockComponent(PostfachSignaturComponent), - MockComponent(ButtonWithSpinnerComponent), - ], + declarations: [PostfachFormComponent, MockComponent(PostfachSignaturComponent), MockComponent(ButtonWithSpinnerComponent)], imports: [ReactiveFormsModule, FormsModule], providers: [ { diff --git a/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach-signatur/postfach-signatur.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach-signatur/postfach-signatur.component.spec.ts index 7ac55113ec028463983d6e823f2e7842fbf9e53a..9d8db826b2b839234c1a12ce65caa10e2480e2bb 100644 --- a/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach-signatur/postfach-signatur.component.spec.ts +++ b/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach-signatur/postfach-signatur.component.spec.ts @@ -1,6 +1,6 @@ -import { getElementFromFixture, mock, useFromMock } from '@alfa-client/test-utils'; +import { getElementFromFixture, mock, Mock } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { TextareaEditorComponent } from '@ods/component'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; @@ -13,14 +13,13 @@ describe('PostfachSignaturComponent', () => { let component: PostfachSignaturComponent; let fixture: ComponentFixture<PostfachSignaturComponent>; - const formService: PostfachFormService = new PostfachFormService( - new FormBuilder(), - useFromMock(mock(PostfachService)), - ); - + let formService: Mock<PostfachFormService>; + let postfachService: Mock<PostfachService>; const signaturTextarea = getDataTestIdOf('signatur-text'); beforeEach(async () => { + formService = mock(PostfachFormService); + postfachService = mock(PostfachService); await TestBed.configureTestingModule({ imports: [ReactiveFormsModule], declarations: [PostfachFormComponent, MockComponent(TextareaEditorComponent)], @@ -29,9 +28,14 @@ describe('PostfachSignaturComponent', () => { provide: PostfachFormService, useValue: formService, }, + { + provide: PostfachService, + useValue: postfachService, + }, ], }).compileComponents(); fixture = TestBed.createComponent(PostfachSignaturComponent); + TestBed.inject(PostfachService); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.spec.ts b/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.spec.ts index bbc93c26a6744ea5ae695e9c4400bdeccfdb1f72..07b58e65ee3cc1b822146f3f5d671dd7192a81d2 100644 --- a/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.spec.ts +++ b/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.spec.ts @@ -1,6 +1,7 @@ import { createStateResource, StateResource } from '@alfa-client/tech-shared'; -import { mock, Mock, useFromMock } from '@alfa-client/test-utils'; -import { FormBuilder } from '@angular/forms'; +import { mock, Mock } from '@alfa-client/test-utils'; +import { TestBed } from '@angular/core/testing'; +import { UntypedFormBuilder } from '@angular/forms'; import { of } from 'rxjs'; import { createPostfach, createPostfachResource } from '../../../../../test/postfach/postfach'; import { Postfach, PostfachResource } from '../../postfach.model'; @@ -10,22 +11,27 @@ import { PostfachFormService } from './postfach.formservice'; describe('PostfachFormService', () => { let formService: PostfachFormService; let postfachService: Mock<PostfachService>; - const formBuilder: FormBuilder = new FormBuilder(); beforeEach(() => { postfachService = mock(PostfachService); - formService = new PostfachFormService(formBuilder, useFromMock(postfachService)); + + TestBed.configureTestingModule({ + providers: [PostfachFormService, UntypedFormBuilder, { provide: PostfachService, useValue: postfachService }], + }); + + formService = TestBed.inject(PostfachFormService); + TestBed.inject(PostfachService); }); it('should create', () => { expect(formService).toBeTruthy(); }); + describe('submit', () => { const postfach: Postfach = createPostfach(); beforeEach(() => { - const stateResource: StateResource<PostfachResource> = - createStateResource(createPostfachResource()); + const stateResource: StateResource<PostfachResource> = createStateResource(createPostfachResource()); postfachService.save.mockReturnValue(of(stateResource)); postfachService.get.mockReturnValue(of(stateResource)); formService.form.setValue({ @@ -69,8 +75,7 @@ describe('PostfachFormService', () => { }); it('should call save with absender if any present', () => { - formValueWithAbsender[PostfachFormService.ASBSENDER_GROUP][PostfachFormService.NAME_FIELD] = - 'something'; + formValueWithAbsender[PostfachFormService.ASBSENDER_GROUP][PostfachFormService.NAME_FIELD] = 'something'; formService.form.setValue(formValueWithAbsender); formService.submit(); diff --git a/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.ts b/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.ts index dd9bcc7a3732f8562aa596faa52b1b4b70407f26..63b83ffccaea662ec02004ff479f0f7a688b47db 100644 --- a/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.ts +++ b/alfa-client/libs/admin/settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.ts @@ -1,6 +1,6 @@ import { AbstractFormService, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared'; -import { Injectable } from '@angular/core'; -import { FormControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { inject, Injectable } from '@angular/core'; +import { FormControl, UntypedFormGroup } from '@angular/forms'; import { isNil } from 'lodash-es'; import { Observable } from 'rxjs'; import { Postfach, PostfachResource } from '../../postfach.model'; @@ -8,6 +8,8 @@ import { PostfachService } from '../../postfach.service'; @Injectable() export class PostfachFormService extends AbstractFormService { + private postfachService: PostfachService = inject(PostfachService); + public static readonly ASBSENDER_GROUP: string = 'absender'; public static readonly NAME_FIELD: string = 'name'; public static readonly ANSCHRIFT_FIELD: string = 'anschrift'; @@ -17,13 +19,6 @@ export class PostfachFormService extends AbstractFormService { public static readonly SIGNATUR_FIELD: string = 'signatur'; - constructor( - formBuilder: UntypedFormBuilder, - private postfachService: PostfachService, - ) { - super(formBuilder); - } - protected initForm(): UntypedFormGroup { return this.formBuilder.group({ [PostfachFormService.ASBSENDER_GROUP]: this.formBuilder.group({ diff --git a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.html b/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.html deleted file mode 100644 index 26ff86713579d5505c52823e6dacb3e429b627cf..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.html +++ /dev/null @@ -1,7 +0,0 @@ -<button - (click)="clickEmitter.emit($event)" - [disabled]="disabled" - class="w-full bg-white px-3 py-2 text-sm font-semibold shadow-sm hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ozgblue-500 active:bg-ozgblue-200" -> - {{ label }} -</button> diff --git a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.spec.ts deleted file mode 100644 index 8d9b522ce93961b555b8c26e0621734e33af7237..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { dispatchEventFromFixture, getElementFromFixture } from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MoreItemButtonComponent } from './more-item-button.component'; - -describe('MoreItemButtonComponent', () => { - let component: MoreItemButtonComponent; - let fixture: ComponentFixture<MoreItemButtonComponent>; - - const buttonSelector: string = 'button'; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [MoreItemButtonComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(MoreItemButtonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should show label', () => { - const text: string = 'test-text'; - component.label = text; - - fixture.detectChanges(); - - const buttonElement: HTMLButtonElement = getElementFromFixture(fixture, buttonSelector); - expect(buttonElement.textContent.trim()).toEqual(text); - }); - - it.each([false, true])('should use disabled "%s"', (disabled) => { - component.disabled = disabled; - - fixture.detectChanges(); - - const buttonElement: HTMLButtonElement = getElementFromFixture(fixture, buttonSelector); - expect(buttonElement.disabled).toBe(disabled); - }); - - it('should emit clickEmitter', () => { - component.clickEmitter.emit = jest.fn(); - - dispatchEventFromFixture(fixture, buttonSelector, 'click'); - - expect(component.clickEmitter.emit).toHaveBeenCalled(); - }); -}); diff --git a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.ts b/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.ts deleted file mode 100644 index 843abd3d47661e76d184df6109f9c253095499ad..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-item-button/more-item-button.component.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ - selector: 'admin-more-item-button', - templateUrl: './more-item-button.component.html', - styles: [], -}) -export class MoreItemButtonComponent { - @Output() - clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); - - @Input() - disabled: boolean; - - @Input() - label: string; -} diff --git a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.html b/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.html deleted file mode 100644 index 08b802175695977a9b8c23d127932f4873b03ec2..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.html +++ /dev/null @@ -1,23 +0,0 @@ -<div class="group relative inline-block text-[0px]"> - <button class="active:bg-ozgblue-`00 rounded-full bg-gray-50 text-base hover:bg-ozgblue-200"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - stroke-width="1.5" - stroke="currentColor" - class="h-6 w-6 stroke-ozgblue-700" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 12.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 18.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z" - /> - </svg> - </button> - <div - class="absolute z-20 hidden flex-col items-stretch text-base drop-shadow-lg group-focus-within:flex" - > - <ng-content select="[more-menu-item]" /> - </div> -</div> diff --git a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.spec.ts deleted file mode 100644 index fd081be8ddb9eb66b376e68d7bdc2cba9139d8f2..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MoreMenuComponent } from './more-menu.component'; - -describe('MoreMenuComponent', () => { - let component: MoreMenuComponent; - let fixture: ComponentFixture<MoreMenuComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [MoreMenuComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(MoreMenuComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.ts b/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.ts deleted file mode 100644 index 897af6c72e72bf7bfe679079e24fbdb4b46a2304..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/more-menu/more-menu.component.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'admin-more-menu', - templateUrl: './more-menu.component.html', - styles: [], -}) -export class MoreMenuComponent {} diff --git a/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.html b/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.html deleted file mode 100644 index 60776b3e9972546299317c813e0fd34c7b5d7be8..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.html +++ /dev/null @@ -1,9 +0,0 @@ -<a - [routerLink]="link" - routerLinkActive="active-link" - data-test-id="anchor" - class="mb-1 flex items-center gap-4 rounded-full p-3 font-semibold hover:bg-ozgblue-200 active:bg-ozgblue-200/75" -> - <img [src]="imageSrc" [alt]="name" class="w-6" [attr.data-test-id]="'image-' + name" /> - <span [attr.data-test-id]="'navigation-label-' + name | convertForDataTest">{{ name }}</span> -</a> diff --git a/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.scss b/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.scss deleted file mode 100644 index 9214426c081848ac1a46efac41e715a120eeff02..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.active-link:not(:hover):not(:active) { - @apply bg-ozgblue-100; -} diff --git a/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.spec.ts deleted file mode 100644 index 21822ae354e1a6a11629a652220a62edc5d100a0..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; -import { getElementFromFixture } from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { RouterTestingModule } from '@angular/router/testing'; -import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { NavigationItemComponent } from './navigation-item.component'; - -describe('NavigationItemComponent', () => { - let component: NavigationItemComponent; - let fixture: ComponentFixture<NavigationItemComponent>; - - const anchorLink = getDataTestIdOf('anchor'); - const navigationLabelName = 'test'; - const image = getDataTestIdOf('image-' + navigationLabelName); - const label = getDataTestIdOf('navigation-label-' + navigationLabelName); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [NavigationItemComponent, ConvertForDataTestPipe], - imports: [ReactiveFormsModule, RouterTestingModule], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(NavigationItemComponent); - component = fixture.componentInstance; - component.name = 'test'; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should use imageSrc input', () => { - const imageSrc = 'imagesrc'; - component.imageSrc = imageSrc; - - fixture.detectChanges(); - - const imageElement: HTMLImageElement = getElementFromFixture(fixture, image); - expect(imageElement.getAttribute('src')).toBe(imageSrc); - }); - - it('should use router link', () => { - const link = '/testroute'; - component.link = link; - - fixture.detectChanges(); - - const anchorElement: HTMLAnchorElement = getElementFromFixture(fixture, anchorLink); - expect(anchorElement.getAttribute('ng-reflect-router-link')).toBe(link); - }); - - describe('image name', () => { - it('should be used for alt', () => { - const imageElement = getElementFromFixture(fixture, image); - expect(imageElement.alt).toBe(navigationLabelName); - }); - - it('should be used for navigation-label', () => { - const labelElement = getElementFromFixture(fixture, label); - expect(labelElement.textContent).toBe(navigationLabelName); - }); - }); -}); diff --git a/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.ts b/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.ts deleted file mode 100644 index a7adc2cb8268e576cf6cd38b74e832b6f0de0797..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/navigation-item/navigation-item.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, Input } from '@angular/core'; - -@Component({ - selector: 'admin-navigation-item', - templateUrl: './navigation-item.component.html', - styleUrls: ['./navigation-item.component.scss'], -}) -export class NavigationItemComponent { - @Input() - link: string; - @Input() - imageSrc: string; - @Input() - name = 'Navigations-Link'; -} diff --git a/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.html b/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.html deleted file mode 100644 index d5e7731728de024e1aeba9f9dbfaa4c86728fe0d..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.html +++ /dev/null @@ -1,26 +0,0 @@ -<button - (click)="clickEmitter.emit($event)" - [disabled]="submitInProgress" - type="button" - class="me-2 inline-flex items-center rounded-lg bg-ozgblue-700 px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-ozgblue-800 focus:ring-4 focus:ring-ozgblue-300 active:bg-ozgblue-600/90 disabled:bg-ozgblue-600/50 dark:bg-ozgblue-600 dark:hover:bg-ozgblue-700 dark:focus:ring-ozgblue-800" -> - <svg - *ngIf="submitInProgress" - aria-hidden="true" - role="status" - class="me-3 inline h-4 w-4 animate-spin text-white" - viewBox="0 0 100 101" - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" - fill="#E5E7EB" - /> - <path - d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" - fill="currentColor" - /> - </svg> - {{ label }} -</button> diff --git a/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.spec.ts deleted file mode 100644 index a48b945b7fabdc55f6c58b44c4cce238c11315de..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { PrimaryButtonComponent } from './primary-button.component'; - -describe('PrimaryButtonComponent', () => { - let component: PrimaryButtonComponent; - let fixture: ComponentFixture<PrimaryButtonComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [PrimaryButtonComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(PrimaryButtonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.ts b/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.ts deleted file mode 100644 index 365dd569dae6f5a1cf82c94eab2215c70249b815..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/primary-button/primary-button.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ - selector: 'admin-primary-button', - templateUrl: './primary-button.component.html', -}) -export class PrimaryButtonComponent { - @Output() - clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); - - @Input() - submitInProgress: boolean; - - @Input() - label: string; -} diff --git a/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.html b/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.html deleted file mode 100644 index 9129a6ce150aad72c0d6dbf2ee7c8ce7f0b36195..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.html +++ /dev/null @@ -1,7 +0,0 @@ -<button - (click)="clickEmitter.emit($event)" - [disabled]="disabled" - class="rounded border border-ozgblue-500 bg-transparent px-4 py-2 font-semibold text-ozgblue-700 hover:border-transparent hover:bg-ozgblue-500 hover:text-white active:bg-ozgblue-500/70" -> - {{ label }} -</button> diff --git a/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.spec.ts deleted file mode 100644 index c50d78977381f148ddf59e6fa27020a88d74e43c..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { SecondaryButtonComponent } from './secondary-button.component'; - -describe('SecondaryButtonComponent', () => { - let component: SecondaryButtonComponent; - let fixture: ComponentFixture<SecondaryButtonComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [SecondaryButtonComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(SecondaryButtonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.ts b/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.ts deleted file mode 100644 index 99bcd42bee927b79e8dda0cf39cfae5ac17d43f7..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/secondary-button/secondary-button.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ - selector: 'admin-secondary-button', - templateUrl: './secondary-button.component.html', -}) -export class SecondaryButtonComponent { - @Output() - clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); - - @Input() - disabled: boolean; - - @Input() - label: string; -} diff --git a/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.html b/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.html deleted file mode 100644 index 44ef993322a3dd799dfd5b807a11a8ed8d08fc3c..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.html +++ /dev/null @@ -1,4 +0,0 @@ -<div - class="text-surface inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-ozgblue-500 border-e-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite] dark:text-white" - role="status" -></div> diff --git a/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.spec.ts deleted file mode 100644 index 8c8fd9ebc126388ce720aaadbbfd46d6d29bcd84..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { SpinnerComponent } from './spinner.component'; - -describe('SpinnerComponent', () => { - let component: SpinnerComponent; - let fixture: ComponentFixture<SpinnerComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [SpinnerComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(SpinnerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.ts b/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.ts deleted file mode 100644 index ea1d1c44e703219a43d5a7ee25c4919e14ec8749..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/spinner/spinner.component.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'admin-spinner', - templateUrl: './spinner.component.html', - styles: [], -}) -export class SpinnerComponent {} diff --git a/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.html b/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.html deleted file mode 100644 index 303f8720da7e44c2cdcc5618f70a64d366e4c64e..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.html +++ /dev/null @@ -1,23 +0,0 @@ -<div class="flex flex-col"> - <label class="grid grid-cols-2 items-center"> - <span - [attr.data-test-id]="'text-field-span-' + label | convertForDataTest" - [ngClass]="control?.invalid ? ['text-red-500', 'font-bold'] : []" - >{{ label }}</span - > - <input - class="m-[2px] p-[2px] outline outline-2 outline-gray-100 focus:outline-0 focus:outline-gray-500" - [attr.data-test-id]="'text-field-input-' + label | convertForDataTest" - type="text" - [formControl]="fieldControl" - /> - </label> - <div - *ngIf="invalidParams.length > 0" - [attr.data-test-id]="'text-field-errors-' + label | convertForDataTest" - > - <span class="mb-3 italic text-red-500" *ngFor="let invalidParam of invalidParams">{{ - getErrorMessage(invalidParam) - }}</span> - </div> -</div> diff --git a/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.spec.ts deleted file mode 100644 index 9096bedd3ab98897a3adc9474b291c7703144572..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ConvertForDataTestPipe, InvalidParam } from '@alfa-client/tech-shared'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { createInvalidParam, createProblemDetail } from 'libs/tech-shared/test/error'; -import { TextFieldComponent } from './text-field.component'; - -import * as TechValidationUtil from 'libs/tech-shared/src/lib/validation/tech.validation.util'; - -describe('TextFieldComponent', () => { - let component: TextFieldComponent; - let fixture: ComponentFixture<TextFieldComponent>; - - const label = 'custom'; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TextFieldComponent, ConvertForDataTestPipe], - imports: [ReactiveFormsModule], - }).compileComponents(); - - fixture = TestBed.createComponent(TextFieldComponent); - component = fixture.componentInstance; - component.label = label; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('getErrorMessage', () => { - it('should call getMessageForInvalidParam()', () => { - const getMessageForInvalidParam: jest.SpyInstance<string, [string, InvalidParam]> = - jest.spyOn(TechValidationUtil, 'getMessageForInvalidParam'); - const invalidParam: InvalidParam = createInvalidParam(); - - component.getErrorMessage(invalidParam); - - expect(getMessageForInvalidParam).toHaveBeenCalledWith(label, invalidParam); - }); - }); - - describe('show error messages', () => { - it('should not call getErrorMessage() if no error', () => { - component.getErrorMessage = jest.fn(); - component.fieldControl.setErrors({}); - - fixture.detectChanges(); - - expect(component.getErrorMessage).not.toHaveBeenCalled(); - }); - - it('should call getErrorMessage() if error', () => { - component.getErrorMessage = jest.fn(); - - component.fieldControl.setErrors({ - ...createProblemDetail(), - invalidParams: [{ ...createInvalidParam(), name: 'settingBody.absender.name' }], - }); - - fixture.detectChanges(); - - expect(component.getErrorMessage).toHaveBeenCalled(); - }); - }); -}); diff --git a/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.ts b/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.ts deleted file mode 100644 index 00037394425175ac3fcca9fe68b1ba286e3c6315..0000000000000000000000000000000000000000 --- a/alfa-client/libs/admin/settings/src/lib/shared/text-field/text-field.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { getMessageForInvalidParam, InvalidParam } from '@alfa-client/tech-shared'; -import { Component, Input } from '@angular/core'; -import { FormControlEditorAbstractComponent } from '@ods/component'; - -@Component({ - selector: 'text-field', - templateUrl: './text-field.component.html', -}) -export class TextFieldComponent extends FormControlEditorAbstractComponent { - @Input() - label: string; - - public getErrorMessage(invalidParam: InvalidParam): string { - return getMessageForInvalidParam(this.label, invalidParam); - } -} diff --git a/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.html b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.html new file mode 100644 index 0000000000000000000000000000000000000000..1536bc42050127ed1f837d7a36de32b1ef513025 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.html @@ -0,0 +1,28 @@ +<div class="max-w-[960px]" [formGroup]="formService.form"> + <h1 class="heading-1 mb-4">Benutzer anlegen</h1> + <div class="mb-4 grid gap-4 xl:grid-cols-2"> + <ods-text-editor [formControlName]="UserAddFormService.VORNAME" label="Vorname" required="true" /> + <ods-text-editor [formControlName]="UserAddFormService.NACHNAME" label="Nachname" required="true" /> + <ods-text-editor [formControlName]="UserAddFormService.BENUTZERNAME" label="Benutzername" required="true" /> + <ods-text-editor [formControlName]="UserAddFormService.EMAIL" label="E-Mail" required="true" /> + </div> + + <h3 class="text-md mb-4 block font-medium text-text">Organisationseinheiten</h3> + <ods-button-with-spinner text="Organisationseinheit hinzufügen" variant="outline" dataTestId="Add-organisationseinheit-button" /> + + <h2 class="heading-2 mt-4">Rollen für OZG-Cloud</h2> + <div [formGroupName]="UserAddFormService.ROLLEN_GROUP" class="mb-8 flex gap-56"> + <div [formGroupName]="UserAddFormService.ADMINISTRATION_GROUP" class="flex flex-col gap-2"> + <h3 class="text-md block font-medium text-text">Administration</h3> + <ods-checkbox-editor [formControlName]="UserAddFormService.ADMIN" label="Admin" inputId="admin" /> + </div> + <div [formGroupName]="UserAddFormService.ALFA_GROUP" class="flex flex-col gap-2"> + <h3 class="text-md block font-medium text-text">Alfa</h3> + <ods-checkbox-editor [formControlName]="UserAddFormService.LOESCHEN" label="Löschen" inputId="delete" /> + <ods-checkbox-editor [formControlName]="UserAddFormService.USER" label="User" inputId="user" /> + <ods-checkbox-editor [formControlName]="UserAddFormService.POSTSTELLE" label="Poststelle" inputId="post_office" /> + </div> + </div> + + <ods-button-with-spinner text="Speichern" dataTestId="save-button" /> +</div> diff --git a/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..079e9ce8630f1e18fe93ba2d48d55997e9c6d378 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.spec.ts @@ -0,0 +1,25 @@ +import { CommonModule } from '@angular/common'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { ButtonWithSpinnerComponent, CheckboxEditorComponent, TextEditorComponent } from '@ods/component'; +import { UserAddFormComponent } from './user-add-form.component'; + +describe('UserAddFormComponent', () => { + let component: UserAddFormComponent; + let fixture: ComponentFixture<UserAddFormComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [UserAddFormComponent], + imports: [CommonModule, ButtonWithSpinnerComponent, ReactiveFormsModule, TextEditorComponent, CheckboxEditorComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UserAddFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.ts b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee7830b3127a701025620bc208c559b2c05eb6d4 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.component.ts @@ -0,0 +1,13 @@ +import { Component, inject } from '@angular/core'; +import { UserAddFormService } from './user-add-form.service'; + +@Component({ + selector: 'admin-user-add-form', + providers: [UserAddFormService], + templateUrl: './user-add-form.component.html', +}) +export class UserAddFormComponent { + formService = inject(UserAddFormService); + + protected readonly UserAddFormService = UserAddFormService; +} diff --git a/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.service.ts b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f497893d00505e06c0170387a582f9d27fa35745 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-form.service.ts @@ -0,0 +1,91 @@ +import { AbstractFormService, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared'; +import { Injectable } from '@angular/core'; +import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Resource } from '@ngxp/rest'; +import { Observable } from 'rxjs'; + +@Injectable() +export class UserAddFormService extends AbstractFormService { + public static readonly VORNAME: string = 'vorname'; + public static readonly NACHNAME: string = 'nachname'; + public static readonly BENUTZERNAME: string = 'benutzername'; + public static readonly EMAIL: string = 'e-mail'; + + public static readonly ROLLEN_GROUP: string = 'rollen'; + public static readonly ADMINISTRATION_GROUP: string = 'administration'; + public static readonly ADMIN: string = 'admin'; + public static readonly ALFA_GROUP: string = 'alfa'; + public static readonly LOESCHEN: string = 'loeschen'; + public static readonly USER: string = 'user'; + public static readonly POSTSTELLE: string = 'poststelle'; + + public static readonly USER_ADD_PREFIX: string = 'userAdd'; + + constructor(public formBuilder: UntypedFormBuilder) { + super(formBuilder); + this.initAlfaGroupLogic(); + } + + protected initForm(): UntypedFormGroup { + return this.formBuilder.group({ + [UserAddFormService.VORNAME]: new FormControl(EMPTY_STRING), + [UserAddFormService.NACHNAME]: new FormControl(EMPTY_STRING), + [UserAddFormService.BENUTZERNAME]: new FormControl(EMPTY_STRING), + [UserAddFormService.EMAIL]: new FormControl(EMPTY_STRING), + [UserAddFormService.ROLLEN_GROUP]: this.formBuilder.group({ + [UserAddFormService.ADMINISTRATION_GROUP]: this.formBuilder.group({ + [UserAddFormService.ADMIN]: new FormControl(false), + }), + [UserAddFormService.ALFA_GROUP]: this.formBuilder.group({ + [UserAddFormService.LOESCHEN]: new FormControl(false), + [UserAddFormService.USER]: new FormControl(false), + [UserAddFormService.POSTSTELLE]: new FormControl(false), + }), + }), + }); + } + + protected initAlfaGroupLogic(): void { + const alfaGroup: UntypedFormGroup = this.getAlfaGroup(); + alfaGroup.valueChanges.subscribe(() => { + this.handleAlfaGroupChange(alfaGroup); + }); + } + + private getAlfaGroup(): UntypedFormGroup { + return <UntypedFormGroup>this.form.get(UserAddFormService.ROLLEN_GROUP).get(UserAddFormService.ALFA_GROUP); + } + + handleAlfaGroupChange(group: UntypedFormGroup): void { + const anyChecked: boolean = this.isAnyChecked(group); + if (anyChecked) { + this.disableUncheckedCheckboxes(group); + } else { + this.enableAllCheckboxes(group); + } + } + + isAnyChecked(group: UntypedFormGroup): boolean { + return Object.keys(group.controls).some((key) => group.controls[key].value); + } + + disableUncheckedCheckboxes(alfaGroup: UntypedFormGroup): void { + for (const control of Object.values<AbstractControl>(alfaGroup.controls)) { + if (control.value === false) control.disable({ emitEvent: false }); + } + } + + enableAllCheckboxes(group: UntypedFormGroup): void { + for (const control of Object.values<AbstractControl>(group.controls)) { + control.enable({ emitEvent: false }); + } + } + + protected doSubmit(): Observable<StateResource<Resource>> { + throw new Error('Method not implemented.'); + } + + protected getPathPrefix(): string { + return UserAddFormService.USER_ADD_PREFIX; + } +} diff --git a/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-formservice.spec.ts b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-formservice.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..82048540fdf5cfbaa2da64004a03673670168f92 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/users-roles/user-add-form/user-add-formservice.spec.ts @@ -0,0 +1,112 @@ +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { UserAddFormService } from './user-add-form.service'; +import SpyInstance = jest.SpyInstance; + +describe('UserAddFormService', () => { + let formService: UserAddFormService; + let alfaGroup: UntypedFormGroup; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [UserAddFormService, UntypedFormBuilder], + }); + + formService = TestBed.inject(UserAddFormService); + alfaGroup = <UntypedFormGroup>formService.form.get(UserAddFormService.ROLLEN_GROUP).get(UserAddFormService.ALFA_GROUP); + }); + + it('should create', () => { + expect(formService).toBeTruthy(); + }); + + describe('initAlfaGroupLogic', () => { + it('should call handleAlfaGroupChange when value of form element changes', fakeAsync(() => { + const handleAlfaGroupChangeSpy: SpyInstance = jest.spyOn(formService as any, 'handleAlfaGroupChange'); + + alfaGroup.get(UserAddFormService.LOESCHEN).setValue(true); + + tick(); + + expect(handleAlfaGroupChangeSpy).toHaveBeenCalled(); + })); + }); + + describe('handleAlfaGroupChange', () => { + it('should call disableUncheckedCheckboxes if any checkbox is checked', () => { + jest.spyOn(formService as any, 'isAnyChecked').mockReturnValue(true); + const disableUncheckedCheckboxesSpy: SpyInstance = jest.spyOn(formService as any, 'disableUncheckedCheckboxes'); + + formService.handleAlfaGroupChange(alfaGroup); + + expect(disableUncheckedCheckboxesSpy).toHaveBeenCalled(); + }); + + it('should call enableAllCheckboxes if not any checkbox is checked', () => { + jest.spyOn(formService as any, 'isAnyChecked').mockReturnValue(false); + const enableAllCheckboxesSpy: SpyInstance = jest.spyOn(formService as any, 'enableAllCheckboxes'); + + formService.handleAlfaGroupChange(alfaGroup); + + expect(enableAllCheckboxesSpy).toHaveBeenCalled(); + }); + }); + + describe('isAnyChecked', () => { + it('should return false if no checkbox is checked', () => { + const result = formService.isAnyChecked(alfaGroup); + + expect(result).toBe(false); + }); + + it('should return true if any checkbox is checked', () => { + alfaGroup.get(UserAddFormService.LOESCHEN).setValue(true); + + const result = formService.isAnyChecked(alfaGroup); + + expect(result).toBe(true); + }); + }); + + describe('disableUncheckedCheckboxes', () => { + it('if control value is false then control should be disabled', () => { + const control: AbstractControl = alfaGroup.get(UserAddFormService.LOESCHEN); + control.setValue(false); + + formService.disableUncheckedCheckboxes(alfaGroup); + + expect(control.disabled).toBe(true); + }); + + it('if control value is true then control should NOT be disabled', () => { + const control: AbstractControl = alfaGroup.get(UserAddFormService.LOESCHEN); + control.setValue(true); + + formService.disableUncheckedCheckboxes(alfaGroup); + + expect(control.disabled).toBe(false); + }); + }); + + describe('updateCheckboxStates', () => { + it('if control value is false then control should be disabled', () => { + const control: AbstractControl = alfaGroup.get(UserAddFormService.LOESCHEN); + control.setValue(false); + + formService.disableUncheckedCheckboxes(alfaGroup); + + expect(control.disabled).toBe(true); + }); + }); + + describe('enableAllCheckboxes', () => { + it('if control value is true then control should be enabled', () => { + const control: AbstractControl = alfaGroup.get(UserAddFormService.LOESCHEN); + const enableSpy = jest.spyOn(control, 'enable'); + + formService.enableAllCheckboxes(alfaGroup); + + expect(enableSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.html b/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.html index 09160afabe086d514782957cb31784a715e747f1..b9d08648462df8ef917ab8c201bd0f8948e68165 100644 --- a/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.html +++ b/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.html @@ -1,7 +1,12 @@ <h1 class="heading-1">Benutzer & Rollen</h1> -<ods-button-with-spinner text="Benutzer hinzufügen" class="py-8" dataTestId="add-user-button" /> +<ods-button-with-spinner + text="Benutzer hinzufügen" + class="py-8" + dataTestId="Add-user-button" + (clickEmitter)="navigateToAddUser()" +/> <ods-list *ngIf="users$ | async as users"> - <ods-list-item *ngFor="let user of users.resource" [path]="user.username"> + <ods-list-item *ngFor="let user of users.resource" [path]="user.username" [attr.data-test-id]="'User-entry-' + user.username"> <div class="flex-1 basis-1/2"> <div class="mb-2 flex flex-wrap items-center gap-3"> <h3 class="text-md font-semibold">{{ user | toUserName }}</h3> diff --git a/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.spec.ts index 49182ee84673fa793f0cbe0ee14eb694ab6f2319..d7c6ca07647f7f99249ee4bf6bf1c0eb33634f58 100644 --- a/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.spec.ts +++ b/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.spec.ts @@ -1,8 +1,12 @@ +import { createStateResource } from '@alfa-client/tech-shared'; import { Mock, mock } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; import { ButtonWithSpinnerComponent } from '@ods/component'; import { MailboxIconComponent, PersonIconComponent } from '@ods/system'; import { MockComponent, MockPipe } from 'ng-mocks'; +import { ROUTES } from '../../../../shared'; +import { createUser } from '../../../test/user/user'; import { ToUserNamePipe } from '../user/to-user-name.pipe'; import { UserService } from './user.service'; import { UsersRolesComponent } from './users-roles.component'; @@ -10,6 +14,7 @@ import { UsersRolesComponent } from './users-roles.component'; describe('UsersRolesComponent', () => { let component: UsersRolesComponent; let fixture: ComponentFixture<UsersRolesComponent>; + let router: Router; const userService: Mock<UserService> = { ...mock(UserService), @@ -28,11 +33,37 @@ describe('UsersRolesComponent', () => { ], }).compileComponents(); + TestBed.inject(UserService); + router = TestBed.inject(Router); + fixture = TestBed.createComponent(UsersRolesComponent); component = fixture.componentInstance; fixture.detectChanges(); }); + describe('component', () => { + describe('ngOnInit', () => { + it('should get users from userService', () => { + const userState = createStateResource([createUser()]); + userService.get.mockReturnValueOnce(userState); + + component.ngOnInit(); + + expect(component.users$).toBe(userState); + }); + }); + + describe('navigateToAddUser', () => { + it('should navigate to add user', () => { + const routerSpy = jest.spyOn(router as any, 'navigate'); + + component.navigateToAddUser(); + + expect(routerSpy).toHaveBeenCalledWith([ROUTES.BENUTZER_UND_ROLLEN_NEU]); + }); + }); + }); + describe('template', () => { it('should create', () => { expect(component).toBeTruthy(); diff --git a/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.ts b/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.ts index 46003c627d6c355241eba06d875e49d4d7df0f2b..b9fead2dca191c13c751519b63ba94c4c794b466 100644 --- a/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.ts +++ b/alfa-client/libs/admin/settings/src/lib/users-roles/users-roles.component.ts @@ -1,6 +1,8 @@ import { StateResource } from '@alfa-client/tech-shared'; -import { Component } from '@angular/core'; +import { Component, inject, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; import { Observable } from 'rxjs'; +import { ROUTES } from '../../../../shared/libs/routes'; import { User } from '../user/user.model'; import { UserService } from './user.service'; @@ -8,11 +10,18 @@ import { UserService } from './user.service'; selector: 'admin-users-roles', templateUrl: './users-roles.component.html', }) -export class UsersRolesComponent { +export class UsersRolesComponent implements OnInit { + private router = inject(Router); + private userService = inject(UserService); + public users$: Observable<StateResource<User[]>>; public readonly GROUPS_TO_DISPLAY = 3; - constructor(private userService: UserService) { + ngOnInit() { this.users$ = this.userService.get(); } + + navigateToAddUser(): void { + this.router.navigate([ROUTES.BENUTZER_UND_ROLLEN_NEU]); + } } diff --git a/alfa-client/libs/admin/shared/index.ts b/alfa-client/libs/admin/shared/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e41b2f175f471052508f2cab70d1f33dae2a2d27 --- /dev/null +++ b/alfa-client/libs/admin/shared/index.ts @@ -0,0 +1 @@ +export * from './libs/routes'; diff --git a/alfa-client/libs/admin/shared/libs/routes.ts b/alfa-client/libs/admin/shared/libs/routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..631028e873a7a4815b9bdefe0b7331b6fbca099c --- /dev/null +++ b/alfa-client/libs/admin/shared/libs/routes.ts @@ -0,0 +1,6 @@ +export const ROUTES = { + POSTFACH: 'postfach', + BENUTZER_UND_ROLLEN: 'benutzer_und_rollen', + BENUTZER_UND_ROLLEN_NEU: 'benutzer_und_rollen/neu', + ORGANISATIONSEINHEITEN: 'organisationseinheiten', +}; diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html index aecfd7e8dcaf39151ce1772d111f06068feb5c45..d099703c582741cc1044529cb41f5783cf472aec 100644 --- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html +++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html @@ -1,9 +1,8 @@ <ods-checkbox [fieldControl]="fieldControl" - [value]="value" [inputId]="inputId" [label]="label" - [disabled]="disabled" + [disabled]="control.disabled" [hasError]="hasError" > <ods-validation-error diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.spec.ts b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.spec.ts index 9c80662fb8d43561499969f3931569d0cb542e29..53ecd61b001e6ecc4a41e9cfa3dd42b8e44b6ed8 100644 --- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.spec.ts +++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.spec.ts @@ -3,6 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { faker } from '@faker-js/faker'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { createInvalidParam } from 'libs/tech-shared/test/error'; +import { MockNgControl } from '../../../../test/form/MockNgControl'; import { CheckboxEditorComponent } from './checkbox-editor.component'; describe('CheckboxEditorComponent', () => { @@ -20,6 +21,7 @@ describe('CheckboxEditorComponent', () => { fixture = TestBed.createComponent(CheckboxEditorComponent); component = fixture.componentInstance; component.label = labelText; + component.control = new MockNgControl(); fixture.detectChanges(); }); diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts index 4ed5d7b07e60146f9a3dc471563c855466d888d7..50b10bd356bcd62bb6de90dddc399817704d7581 100644 --- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts @@ -1,5 +1,6 @@ import { TechSharedModule } from '@alfa-client/tech-shared'; import { Component, Input } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; import { CheckboxComponent } from '@ods/system'; import { FormControlEditorAbstractComponent } from '../formcontrol-editor.abstract.component'; import { ValidationErrorComponent } from '../validation-error/validation-error.component'; @@ -7,14 +8,12 @@ import { ValidationErrorComponent } from '../validation-error/validation-error.c @Component({ selector: 'ods-checkbox-editor', standalone: true, - imports: [CheckboxComponent, ValidationErrorComponent, TechSharedModule], + imports: [CheckboxComponent, ValidationErrorComponent, TechSharedModule, ReactiveFormsModule], templateUrl: './checkbox-editor.component.html', }) export class CheckboxEditorComponent extends FormControlEditorAbstractComponent { - @Input() value: string; @Input() inputId: string; @Input() label: string; - @Input() disabled: boolean = false; get hasError(): boolean { return this.invalidParams.length > 0; diff --git a/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts b/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts index cb0f3f5842b32a3ce76321d194097c9a2f64229a..3c1f8d9a1d19fd69416b0dab478500ea65867218 100644 --- a/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts @@ -24,15 +24,12 @@ import { InvalidParam } from '@alfa-client/tech-shared'; import { Component, OnDestroy, OnInit, Optional, Self } from '@angular/core'; import { ControlValueAccessor, NgControl, UntypedFormControl } from '@angular/forms'; -import { isEmpty } from 'lodash-es'; import { Subscription } from 'rxjs'; @Component({ template: 'NO UI', }) -export abstract class FormControlEditorAbstractComponent - implements ControlValueAccessor, OnInit, OnDestroy -{ +export abstract class FormControlEditorAbstractComponent implements ControlValueAccessor, OnInit, OnDestroy { readonly fieldControl: UntypedFormControl = new UntypedFormControl(); public onChange = (text: string | Date) => undefined; public onTouched = () => undefined; @@ -46,7 +43,6 @@ export abstract class FormControlEditorAbstractComponent if (this.control) this.control.valueAccessor = this; this.changesSubscr = this.fieldControl.valueChanges.subscribe((val) => { - val = isEmpty(val) ? null : val; this.onChange(val); this.setErrors(); }); @@ -91,9 +87,7 @@ export abstract class FormControlEditorAbstractComponent get invalidParams(): InvalidParam[] { return this.fieldControl.errors ? - Object.keys(this.fieldControl.errors).map( - (key) => <InvalidParam>this.fieldControl.errors[key], - ) + Object.keys(this.fieldControl.errors).map((key) => <InvalidParam>this.fieldControl.errors[key]) : []; } } diff --git a/alfa-client/libs/design-component/test/form/MockNgControl.ts b/alfa-client/libs/design-component/test/form/MockNgControl.ts new file mode 100644 index 0000000000000000000000000000000000000000..80a3bc01a22b76cc868465ded2b84a0b057071ab --- /dev/null +++ b/alfa-client/libs/design-component/test/form/MockNgControl.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { AbstractControl, ControlValueAccessor, NgControl, UntypedFormControl } from '@angular/forms'; + +@Injectable() +export class MockNgControl extends NgControl { + valueAccessor: ControlValueAccessor | null = null; + + get control(): AbstractControl { + return new UntypedFormControl(null); + } + + viewToModelUpdate(newValue: any): void {} +} 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 35cc64b06437d36745806052b651115d4d307b99..fd81fa2b276da1d907d9993b92e98a9fb76362d0 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 @@ -24,8 +24,7 @@ import { twMerge } from 'tailwind-merge'; </button> <div *ngIf="isPopupOpen" - class="bg-dropdownBg absolute max-h-120 min-w-44 max-w-80 - animate-fadeIn overflow-y-auto rounded shadow-md focus:outline-none" + class="absolute z-50 max-h-120 min-w-44 max-w-80 animate-fadeIn overflow-y-auto rounded bg-dropdownBg shadow-md 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/checkbox/checkbox.component.ts b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts index 9cab0abad5aa6845f9a6f622fa7bfde8ca3d35ec..3fbc1b80bfb06f3aac9a6af622920803a313100b 100644 --- a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts +++ b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts @@ -1,3 +1,4 @@ +import { TechSharedModule } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; import { Component, Input } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; @@ -5,24 +6,22 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms'; @Component({ selector: 'ods-checkbox', standalone: true, - imports: [CommonModule, ReactiveFormsModule], + imports: [CommonModule, ReactiveFormsModule, TechSharedModule], template: ` <div> - <div class="flex items-start gap-3 text-start"> + <div class="relative flex items-start gap-3 text-start"> <input type="checkbox" - class="disabled:border-disabled-dark disabled:bg-disabled peer relative box-border size-5 shrink-0 appearance-none rounded-sm - border bg-whitetext outline outline-2 outline-offset-2 outline-transparent - hover:border-2 focus-visible:border-background-200 disabled:hover:border" + class="peer box-border size-5 shrink-0 appearance-none rounded-sm border bg-whitetext outline outline-2 outline-offset-2 outline-transparent hover:border-2 focus-visible:border-background-200 disabled:border-disabled-dark disabled:bg-disabled disabled:hover:border" [ngClass]=" hasError ? 'border-error hover:border-error focus-visible:outline-error' : 'border-primary hover:border-primary-hover focus-visible:outline-focus' " - [value]="value" - [checked]="fieldControl.value" + [formControl]="fieldControl" [attr.id]="inputId" - [disabled]="disabled" + [attr.disabled]="disabled ? true : null" + [attr.data-test-id]="(label | convertForDataTest) + '-checkbox-editor'" /> <label class="leading-5 text-text" [attr.for]="inputId">{{ label }}</label> <svg @@ -43,7 +42,6 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms'; }) export class CheckboxComponent { @Input() fieldControl: FormControl = new FormControl(false); - @Input() value: string; @Input() inputId: string; @Input() label: string; @Input() disabled: boolean = false; diff --git a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.stories.ts b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.stories.ts index ce8dd243e62d68c65d58f4298744e5ded9ebedf6..c7a56bc1fa4a36b16977718cc68b6ca445b25f30 100644 --- a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.stories.ts +++ b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.stories.ts @@ -22,7 +22,6 @@ type Story = StoryObj<CheckboxComponent>; export const Default: Story = { args: { - value: 'Checkbox value', label: 'Basic checkbox', inputId: '1', disabled: false, @@ -32,7 +31,6 @@ export const Default: Story = { label: { description: 'Checkbox label' }, disabled: { description: 'Disabled state of checkbox' }, inputId: { description: 'Id of checkbox input' }, - value: { description: 'Value of checkbox' }, hasError: { description: 'Has checkbox the error state' }, fieldControl: { description: 'Form control object', diff --git a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts index a534a5276d34b072c1b8edee6e95e362a5296d77..58dcedb8b91762b9155936d039e9b383e12dcb8f 100644 --- a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts +++ b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts @@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common'; import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { cva, VariantProps } from 'class-variance-authority'; -import { ErrorMessageComponent } from '../error-message/error-message.component'; const textInputVariants = cva( [ @@ -15,8 +14,7 @@ const textInputVariants = cva( variant: { default: 'border-primary-600/50 focus-visible:outline-focus focus-visible:border-background-200 hover:border-primary-hover', - error: - 'border-error/50 hover:border-error focus-visible:outline-error focus-visible:border-background-200', + error: 'border-error/50 hover:border-error focus-visible:outline-error focus-visible:border-background-200', }, }, defaultVariants: { @@ -29,28 +27,21 @@ type TextInputVariants = VariantProps<typeof textInputVariants>; @Component({ selector: 'ods-text-input', standalone: true, - imports: [CommonModule, ErrorMessageComponent, ReactiveFormsModule, TechSharedModule], + imports: [CommonModule, ReactiveFormsModule, TechSharedModule], template: ` <div class="relative"> <label *ngIf="showLabel" [for]="id" class="text-md mb-2 block font-medium text-text"> {{ inputLabel }}<ng-container *ngIf="required"><i aria-hidden="true">*</i></ng-container> </label> <div class="mt-2"> - <div - *ngIf="withPrefix" - class="pointer-events-none absolute bottom-2 left-2 flex size-6 items-center justify-center" - > + <div *ngIf="withPrefix" class="pointer-events-none absolute bottom-2 left-2 flex size-6 items-center justify-center"> <ng-content select="[prefix]" /> </div> <input type="text" [id]="id" [formControl]="fieldControl" - [ngClass]="[ - textInputVariants({ variant }), - withPrefix ? 'pl-10' : '', - withSuffix ? 'pr-10' : '', - ]" + [ngClass]="[textInputVariants({ variant }), withPrefix ? 'pl-10' : '', withSuffix ? 'pr-10' : '']" [placeholder]="placeholder" [autocomplete]="autocomplete" [attr.aria-required]="required" @@ -59,10 +50,7 @@ type TextInputVariants = VariantProps<typeof textInputVariants>; (click)="clickEmitter.emit()" #inputElement /> - <div - *ngIf="withSuffix" - class="absolute bottom-2 right-2 flex size-6 items-center justify-center" - > + <div *ngIf="withSuffix" class="absolute bottom-2 right-2 flex size-6 items-center justify-center"> <ng-content select="[suffix]" /> </div> </div> diff --git a/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts b/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts index e14057ca1213095531293412d81b4952f80e5e1d..c2a00e20b56393869eb2be605d6585d1a85564b4 100644 --- a/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts +++ b/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts @@ -23,6 +23,7 @@ */ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Injectable } from '@angular/core'; import { Resource } from '@ngxp/rest'; import { isNil } from 'lodash-es'; import { identity, Observable, OperatorFunction } from 'rxjs'; @@ -32,6 +33,7 @@ import { HttpError, InvalidParam, ProblemDetail } from '../tech.model'; import { isNotUndefined } from '../tech.util'; import { setInvalidParamValidationError } from '../validation/tech.validation.util'; +@Injectable() export abstract class AbstractFormService<T extends Resource = Resource> { form: UntypedFormGroup; pathPrefix: string;