diff --git a/alfa-client/Jenkinsfile.e2e b/alfa-client/Jenkinsfile.e2e index 94fcdb512628bbe15ac09a1640bc9e89f8b68920..da6f80f30f5cb7e07babc7fd53e3195f86d3afc4 100644 --- a/alfa-client/Jenkinsfile.e2e +++ b/alfa-client/Jenkinsfile.e2e @@ -26,436 +26,434 @@ import groovy.json.JsonOutput def SKIP_RUN = false pipeline { - agent { - node { - label 'ozgcloud-jenkins-build-agent-jdk21-node20' - } + agent { + node { + label 'ozgcloud-jenkins-build-agent-jdk21-node20' } + } - triggers { - // upstream(upstreamProjects: getUpstreamProjects(), threshold: hudson.model.Result.SUCCESS) - cron('0 18-23,0-5 * * *') - } + triggers { + // upstream(upstreamProjects: getUpstreamProjects(), threshold: hudson.model.Result.SUCCESS) + cron('0 18-23,0-5 * * *') + } - environment { - JENKINS_URL = "https://jenkins.infra.ozg-cloud.systems/job/codeSH_E2E/job/${env.BRANCH_NAME}/${env.BUILD_NUMBER}/" - BUNDESLAND = "by" - SSO_URL = "sso.dev.by.ozg-cloud.de" - CLUSTER_BASE_URL = "dev.by.ozg-cloud.de" - FAILED_STAGE = "" - FAILED_PARALLEL_STAGE = " " - EA_BEZEICHNER = generateBezeichner("e2e-ea") - MAIN_BEZEICHNER = generateBezeichner("e2e-main") - ADMIN_BEZEICHNER = generateBezeichner("e2e-admin") - SH_SUCCESS_STATUS_CODE = 0 - FORCE_COLOR = 0 - NO_COLOR = 1 - NX_DISABLE_DB = true - KEYCLOAK_CLIENT_ADMIN_APP = "admin" - KEYCLOAK_CLIENT_ALFA_APP = "alfa" - } + environment { + JENKINS_URL = "https://jenkins.infra.ozg-cloud.systems/job/codeSH_E2E/job/${env.BRANCH_NAME}/${env.BUILD_NUMBER}/" + BUNDESLAND = "by" + SSO_URL = "sso.dev.by.ozg-cloud.de" + CLUSTER_BASE_URL = "dev.by.ozg-cloud.de" + FAILED_STAGE = "" + FAILED_PARALLEL_STAGE = " " + EA_BEZEICHNER = generateBezeichner("e2e-ea") + MAIN_BEZEICHNER = generateBezeichner("e2e-main") + ADMIN_BEZEICHNER = generateBezeichner("e2e-admin") + SH_SUCCESS_STATUS_CODE = 0 + FORCE_COLOR = 0 + NO_COLOR = 1 + NX_DISABLE_DB = true + KEYCLOAK_CLIENT_ADMIN_APP = "admin" + KEYCLOAK_CLIENT_ALFA_APP = "alfa" + } - options { - timeout(time: 2, unit: 'HOURS') - disableConcurrentBuilds() - buildDiscarder(logRotator(numToKeepStr: '15')) - skipDefaultCheckout(true) - } + options { + timeout(time: 2, unit: 'HOURS') + disableConcurrentBuilds() + buildDiscarder(logRotator(numToKeepStr: '15')) + skipDefaultCheckout(true) + } - stages { - stage('Checkout build trigger') { - when { - not { - anyOf { - triggeredBy 'UpstreamCause' - triggeredBy 'BuildUpstreamCause' - triggeredBy cause: 'UserIdCause' - triggeredBy 'TimerTrigger' - } - } - } - steps { - script { - SKIP_RUN = true - currentBuild.result= "UNSTABLE" - } - } - } - stage('Check branch') { - when { - triggeredBy 'TimerTrigger' - } - steps { - script { - if (!isMainBranch()) { - echo "Branchname "+env.BRANCH_NAME+" does not match, skipping e2e tests" - SKIP_RUN = true - } - } - } + stages { + stage('Checkout build trigger') { + when { + not { + anyOf { + triggeredBy 'UpstreamCause' + triggeredBy 'BuildUpstreamCause' + triggeredBy cause: 'UserIdCause' + triggeredBy 'TimerTrigger' + } } - stage('Checkout SCM') { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME - - checkout scm - } - } + } + steps { + script { + SKIP_RUN = true + currentBuild.result = "UNSTABLE" } - stage("Clone Gitops Repo") { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME - - cloneGitopsRepo() - } - } + } + } + stage('Check branch') { + when { + triggeredBy 'TimerTrigger' + } + steps { + script { + if (!isMainBranch()) { + echo "Branchname " + env.BRANCH_NAME + " does not match, skipping e2e tests" + SKIP_RUN = true + } + } + } + } + stage('Checkout SCM') { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME + + checkout scm + } + } + } + stage("Clone Gitops Repo") { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME + + cloneGitopsRepo() } + } + } - stage("Init Default Versions") { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME - - initEnvAdminDefaultVersions() - initEnvAlfaDefaultVersions() - initEnvVorgangManagerDefaultVersions() - initEnvUserManagerDefaultVersions() - } - } + stage("Init Default Versions") { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME + + initEnvAdminDefaultVersions() + initEnvAlfaDefaultVersions() + initEnvAlfaClientDefaultVersion() + initEnvVorgangManagerDefaultVersions() + initEnvUserManagerDefaultVersions() } + } + } - stage("Set User Versions") { - when { - expression { !SKIP_RUN } - beforeInput true - triggeredBy cause: "UserIdCause" - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME - - catchError { - timeout(time: 5, unit: 'MINUTES') { - userVersions = input message: "Edit Default Values", - parameters: [ - string(name: "AlfaImageTag", defaultValue: env.ALFA_IMAGE_TAG, trim: true), - string(name: "AlfaHelmChartVersion", defaultValue: env.ALFA_HELM_CHART_VERSION, trim: true), - string(name: "AlfaHelmRepoUrl", defaultValue: env.ALFA_HELM_REPO_URL, trim: true), - string(name: "AdministrationImageTag", defaultValue: env.ADMINISTRATION_IMAGE_TAG, trim: true), - string(name: "AdministrationHelmChartVersion", defaultValue: env.ADMINISTRATION_HELM_CHART_VERSION, trim: true), - string(name: "AdministrationHelmRepoUrl", defaultValue: env.ADMINISTRATION_HELM_REPO_URL, trim: true), - string(name: "AdminClientImageTag", defaultValue: env.ADMIN_CLIENT_IMAGE_TAG, trim: true), - string(name: "AdminClientHelmChartVersion", defaultValue: env.ADMIN_CLIENT_HELM_CHART_VERSION, trim: true), - string(name: "AdminClientHelmRepoUrl", defaultValue: env.ADMIN_CLIENT_HELM_REPO_URL, trim: true), - string(name: "VorgangManagerImageTag", defaultValue: env.VORGANG_MANAGER_IMAGE_TAG, trim: true), - string(name: "VorgangManagerHelmChartVersion", defaultValue: env.VORGANG_MANAGER_HELM_CHART_VERSION, trim: true), - string(name: "VorgangManagerHelmRepoUrl", defaultValue: env.VORGANG_MANAGER_HELM_REPO_URL, trim: true), - string(name: "UserManagerImageTag", defaultValue: env.USER_MANAGER_IMAGE_TAG, trim: true), - string(name: "UserManagerHelmChartVersion", defaultValue: env.USER_MANAGER_HELM_CHART_VERSION, trim: true), - string(name: "UserManagerHelmRepoUrl", defaultValue: env.USER_MANAGER_HELM_REPO_URL, trim: true) - ] - - initEnvUserVersions(userVersions) - } - } - } + stage("Set User Versions") { + when { + expression { !SKIP_RUN } + beforeInput true + triggeredBy cause: "UserIdCause" + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME + + catchError { + timeout(time: 5, unit: 'MINUTES') { + userVersions = input message: "Edit Default Values", + parameters: [ + string(name: "AlfaImageTag", defaultValue: env.ALFA_IMAGE_TAG, trim: true), + string(name: "AlfaHelmChartVersion", defaultValue: env.ALFA_HELM_CHART_VERSION, trim: true), + string(name: "AlfaHelmRepoUrl", defaultValue: env.ALFA_HELM_REPO_URL, trim: true), + string(name: "AlfaClientImageTag", defaultValue: env.ALFA_CLIENT_IMAGE_TAG, trim: true), + string(name: "AlfaClientHelmChartVersion", defaultValue: env.ALFA_CLIENT_HELM_CHART_VERSION, trim: true), + string(name: "AlfaClientHelmRepoUrl", defaultValue: env.ALFA_CLIENT_HELM_REPO_URL, trim: true), + string(name: "AdministrationImageTag", defaultValue: env.ADMINISTRATION_IMAGE_TAG, trim: true), + string(name: "AdministrationHelmChartVersion", defaultValue: env.ADMINISTRATION_HELM_CHART_VERSION, trim: true), + string(name: "AdministrationHelmRepoUrl", defaultValue: env.ADMINISTRATION_HELM_REPO_URL, trim: true), + string(name: "AdminClientImageTag", defaultValue: env.ADMIN_CLIENT_IMAGE_TAG, trim: true), + string(name: "AdminClientHelmChartVersion", defaultValue: env.ADMIN_CLIENT_HELM_CHART_VERSION, trim: true), + string(name: "AdminClientHelmRepoUrl", defaultValue: env.ADMIN_CLIENT_HELM_REPO_URL, trim: true), + string(name: "VorgangManagerImageTag", defaultValue: env.VORGANG_MANAGER_IMAGE_TAG, trim: true), + string(name: "VorgangManagerHelmChartVersion", defaultValue: env.VORGANG_MANAGER_HELM_CHART_VERSION, trim: true), + string(name: "VorgangManagerHelmRepoUrl", defaultValue: env.VORGANG_MANAGER_HELM_REPO_URL, trim: true), + string(name: "UserManagerImageTag", defaultValue: env.USER_MANAGER_IMAGE_TAG, trim: true), + string(name: "UserManagerHelmChartVersion", defaultValue: env.USER_MANAGER_HELM_CHART_VERSION, trim: true), + string(name: "UserManagerHelmRepoUrl", defaultValue: env.USER_MANAGER_HELM_REPO_URL, trim: true) + ] + + initEnvUserVersions(userVersions) } + } } + } + } - stage('Init k8s') { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME + stage('Init k8s') { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME - configFileProvider([configFile(fileId: 'kubeconfig-dev-okd-cluster', variable: 'KUBE_CONFIG')]) { - sh 'mkdir ~/.kube' - sh 'cp ${KUBE_CONFIG} ~/.kube/config' - } + configFileProvider([configFile(fileId: 'kubeconfig-dev-okd-cluster', variable: 'KUBE_CONFIG')]) { + sh 'mkdir ~/.kube' + sh 'cp ${KUBE_CONFIG} ~/.kube/config' + } - sh 'helm version' - } - } + sh 'helm version' } + } + } - stage('Rollout E2E Namespaces') { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME + stage('Rollout E2E Namespaces') { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME - checkoutGitopsE2eBranch() + checkoutGitopsE2eBranch() - deleteNamespaces([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER, env.ADMIN_BEZEICHNER]) + deleteNamespaces([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER, env.ADMIN_BEZEICHNER]) - generateNamespaces() + generateNamespaces() - pushGitopsRepo() - } - } - post { - failure { - script { - deleteNamespaces([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER, env.ADMIN_BEZEICHNER]) - } - } - } + pushGitopsRepo() + } + } + post { + failure { + script { + deleteNamespaces([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER, env.ADMIN_BEZEICHNER]) + } } + } + } - stage("Install Cypress") { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME - - withNPM(npmrcConfig: 'npm-nexus-auth') { - sh 'pnpm --version' - dir('alfa-client') { - sh 'pnpm install --frozen-lockfile --network-concurrency=8' - sh "pnpm run cypress:install" - } - } - } + stage("Install Cypress") { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME + + withNPM(npmrcConfig: 'npm-nexus-auth') { + sh 'pnpm --version' + dir('alfa-client') { + sh 'pnpm install --frozen-lockfile --network-concurrency=8' + sh "pnpm run cypress:install" } + } } + } + } - stage('Wait for Rollout E2E Namespaces') { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME + stage('Wait for Rollout E2E Namespaces') { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME - waitForAdminRollout(env.ADMIN_BEZEICHNER) - waitForAlfaRollout([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER]) - } - } - post { - failure { - script { - deleteNamespaces([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER, env.ADMIN_BEZEICHNER]) - } - } - } + waitForAdminRollout(env.ADMIN_BEZEICHNER) + waitForAlfaRollout([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER]) } + } + post { + failure { + script { + deleteNamespaces([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER, env.ADMIN_BEZEICHNER]) + } + } + } + } - stage('Init PortForwards') { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME + stage('Init PortForwards') { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME - forwardElasticSearch() - } - } + forwardElasticSearch() } + } + } - //stage('Run E2E-Tests') { - // when { - // expression { !SKIP_RUN } - // } - // failFast false - - // parallel { - stage('E2E-Alfa-EA') { - when { - expression { !SKIP_RUN } - } - steps { - catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { - script { - def bezeichner = env.EA_BEZEICHNER - - Integer mongoDbPort = forwardMongoDbPort(generateNamespace(bezeichner)) - - runTests(bezeichner, 'alfa-e2e', 'einheitlicher-ansprechpartner', env.KEYCLOAK_CLIENT_ALFA_APP, mongoDbPort, env.STAGE_NAME) - - stopForwardMongoDbPort(generateNamespace(bezeichner)) - } - } - } - post { - failure { - script { - FAILED_PARALLEL_STAGE += "${env.STAGE_NAME} " - } - } - always { - script { - publishAlfaE2ETestResult("einheitlicher-ansprechpartner", "Alfa E2E-Tests EA") - } - } - } - } - - stage('E2E-Alfa-Main') { - when { - expression { !SKIP_RUN } - } - steps { - catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { - script { - def bezeichner = env.MAIN_BEZEICHNER - - Integer mongoDbPort = forwardMongoDbPort(generateNamespace(bezeichner)) - - runTests(bezeichner, 'alfa-e2e', 'main-tests', env.KEYCLOAK_CLIENT_ALFA_APP, mongoDbPort, env.STAGE_NAME) - - stopForwardMongoDbPort(generateNamespace(bezeichner)) - } - } - } - post { - failure { - script { - FAILED_PARALLEL_STAGE += "${env.STAGE_NAME} " - } - } - always { - script { - publishAlfaE2ETestResult("main-tests", "Alfa E2E-Tests main") - } - } - } - } - stage('E2E-Admin-Main') { - when { - expression { !SKIP_RUN } - } - steps { - catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { - script { - String bezeichner = env.ADMIN_BEZEICHNER - - Integer mongoDbPort = forwardMongoDbPort(generateNamespace(bezeichner)) - - runTests(bezeichner, 'admin-e2e', 'main-tests', env.KEYCLOAK_CLIENT_ADMIN_APP, mongoDbPort, env.STAGE_NAME) - - stopForwardMongoDbPort(generateNamespace(bezeichner)) - } - } - } - post { - failure { - script { - FAILED_PARALLEL_STAGE += "${env.STAGE_NAME} " - } - } - always { - script { - publishAdminE2ETestResult() - } - } - } - } - // } - //} - - stage('Delete E2E Namespaces') { - when { - expression { !SKIP_RUN } - } - steps { - script { - FAILED_STAGE = env.STAGE_NAME + //stage('Run E2E-Tests') { + // when { + // expression { !SKIP_RUN } + // } + // failFast false - deleteNamespaces([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER, env.ADMIN_BEZEICHNER]) - } - } + // parallel { + stage('E2E-Alfa-EA') { + when { + expression { !SKIP_RUN } + } + steps { + catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { + script { + def bezeichner = env.EA_BEZEICHNER + + Integer mongoDbPort = forwardMongoDbPort(generateNamespace(bezeichner)) + + runTests(bezeichner, 'alfa-e2e', 'einheitlicher-ansprechpartner', env.KEYCLOAK_CLIENT_ALFA_APP, mongoDbPort, env.STAGE_NAME) + + stopForwardMongoDbPort(generateNamespace(bezeichner)) + } + } + } + post { + failure { + script { + FAILED_PARALLEL_STAGE += "${env.STAGE_NAME} " + } } + always { + script { + publishAlfaE2ETestResult("einheitlicher-ansprechpartner", "Alfa E2E-Tests EA") + } + } + } + } + + stage('E2E-Alfa-Main') { + when { + expression { !SKIP_RUN } + } + steps { + catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { + script { + def bezeichner = env.MAIN_BEZEICHNER + + Integer mongoDbPort = forwardMongoDbPort(generateNamespace(bezeichner)) + + runTests(bezeichner, 'alfa-e2e', 'main-tests', env.KEYCLOAK_CLIENT_ALFA_APP, mongoDbPort, env.STAGE_NAME) + stopForwardMongoDbPort(generateNamespace(bezeichner)) + } + } + } + post { + failure { + script { + FAILED_PARALLEL_STAGE += "${env.STAGE_NAME} " + } + } + always { + script { + publishAlfaE2ETestResult("main-tests", "Alfa E2E-Tests main") + } + } + } } - post { + stage('E2E-Admin-Main') { + when { + expression { !SKIP_RUN } + } + steps { + catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { + script { + String bezeichner = env.ADMIN_BEZEICHNER + + Integer mongoDbPort = forwardMongoDbPort(generateNamespace(bezeichner)) + + runTests(bezeichner, 'admin-e2e', 'main-tests', env.KEYCLOAK_CLIENT_ADMIN_APP, mongoDbPort, env.STAGE_NAME) + + stopForwardMongoDbPort(generateNamespace(bezeichner)) + } + } + } + post { failure { - script { - if (isMainBranch() || isReleaseBranch()) { - sendFailureMessage() - } - } + script { + FAILED_PARALLEL_STAGE += "${env.STAGE_NAME} " + } } + always { + script { + publishAdminE2ETestResult() + } + } + } + } + // } + //} + + stage('Delete E2E Namespaces') { + when { + expression { !SKIP_RUN } + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME + + deleteNamespaces([env.EA_BEZEICHNER, env.MAIN_BEZEICHNER, env.ADMIN_BEZEICHNER]) + } + } } + + } + post { + failure { + script { + if (isMainBranch() || isReleaseBranch()) { + sendFailureMessage() + } + } + } + } } String getUpstreamProjects() { - if (isMainBranch() || isReleaseBranch()){ - return "alfa/${env.BRANCH_NAME},vorgang-manager/${env.BRANCH_NAME},user-manager/${env.BRANCH_NAME}" - } + if (isMainBranch() || isReleaseBranch()) { + return "alfa/${env.BRANCH_NAME},vorgang-manager/${env.BRANCH_NAME},user-manager/${env.BRANCH_NAME}" + } - return "" + return "" } def cloneGitopsRepo() { - final email = "jenkins@ozg-sh.de" - final name = "jenkins" + final email = "jenkins@ozg-sh.de" + final name = "jenkins" - withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) { - sh 'git clone https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git' + withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) { + sh 'git clone https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git' - dir("gitops") { - sh "git config user.email '${email}'" - sh "git config user.name '${name}'" - } + dir("gitops") { + sh "git config user.email '${email}'" + sh "git config user.name '${name}'" } + } } Map getApplicationValues(String application) { - def applicationValues = readYaml file: "gitops/${getEnvironment()}/application/values/${application}-values.yaml" + def applicationValues = readYaml file: "gitops/${getEnvironment()}/application/values/${application}-values.yaml" - return applicationValues.get(application.replaceAll("-", "_")) + return applicationValues.get(application.replaceAll("-", "_")) } String getEnvironment() { - if (isReleaseBranch()) { - return "test" - } + if (isReleaseBranch()) { + return "test" + } - return "dev" + return "dev" } String getImageTag(Map applicationValues) { - return applicationValues.image.tag + return applicationValues.image.tag } String getHelmChartVersion(Map applicationValues) { - return applicationValues.helm.version + return applicationValues.helm.version } Boolean isMainBranch() { - return env.BRANCH_NAME == 'main' + return env.BRANCH_NAME == 'main' } String getHelmRepoUrl() { - if (isReleaseBranch()) { - return "https://nexus.ozg-sh.de/repository/ozg-base-apps" - } - - return "https://nexus.ozg-sh.de/repository/ozg-base-apps-snapshot" -} - -String getRootPomVersion() { - def rootPom = readMavenPom file: 'pom.xml' + if (isReleaseBranch()) { + return "https://nexus.ozg-sh.de/repository/ozg-base-apps" + } - return rootPom.version + return "https://nexus.ozg-sh.de/repository/ozg-base-apps-snapshot" } Void initEnvAdminDefaultVersions() { @@ -471,68 +469,77 @@ Void initEnvAdminDefaultVersions() { } Void initEnvAlfaDefaultVersions() { - def values = getApplicationValues('alfa') + def values = getApplicationValues('alfa') + + env.ALFA_IMAGE_TAG = getImageTag(values) + env.ALFA_HELM_CHART_VERSION = getHelmChartVersion(values) + env.ALFA_HELM_REPO_URL = getHelmRepoUrl() +} + +Void initEnvAlfaClientDefaultVersion() { + def values = getApplicationValues('alfa-client') - env.ALFA_IMAGE_TAG = getImageTag(values) - env.ALFA_HELM_CHART_VERSION = getHelmChartVersion(values) - env.ALFA_HELM_REPO_URL = getHelmRepoUrl() + env.ALFA_CLIENT_IMAGE_TAG = getImageTag(values) + env.ALFA_CLIENT_HELM_CHART_VERSION = getHelmChartVersion(values) + env.ALFA_CLIENT_HELM_REPO_URL = getHelmRepoUrl() } Void initEnvVorgangManagerDefaultVersions() { - def values = getApplicationValues('vorgang-manager') + def values = getApplicationValues('vorgang-manager') - env.VORGANG_MANAGER_IMAGE_TAG = getImageTag(values) - env.VORGANG_MANAGER_HELM_CHART_VERSION = getHelmChartVersion(values) - env.VORGANG_MANAGER_HELM_REPO_URL = getHelmRepoUrl() + env.VORGANG_MANAGER_IMAGE_TAG = getImageTag(values) + env.VORGANG_MANAGER_HELM_CHART_VERSION = getHelmChartVersion(values) + env.VORGANG_MANAGER_HELM_REPO_URL = getHelmRepoUrl() } Void initEnvUserManagerDefaultVersions() { - def values = getApplicationValues('user-manager') + def values = getApplicationValues('user-manager') - env.USER_MANAGER_IMAGE_TAG = getImageTag(values) - env.USER_MANAGER_HELM_CHART_VERSION = getHelmChartVersion(values) - env.USER_MANAGER_HELM_REPO_URL = getHelmRepoUrl() + env.USER_MANAGER_IMAGE_TAG = getImageTag(values) + env.USER_MANAGER_HELM_CHART_VERSION = getHelmChartVersion(values) + env.USER_MANAGER_HELM_REPO_URL = getHelmRepoUrl() } Void initEnvUserVersions(userVersions) { - env.ALFA_IMAGE_TAG = userVersions.AlfaImageTag - env.ALFA_HELM_CHART_VERSION = userVersions.AlfaHelmChartVersion - env.ALFA_HELM_REPO_URL = userVersions.AlfaHelmRepoUrl - env.ADMINISTRATION_IMAGE_TAG = userVersions.AdministrationImageTag - env.ADMINISTRATION_HELM_CHART_VERSION = userVersions.AdministrationHelmChartVersion - env.ADMINISTRATION_HELM_REPO_URL = userVersions.AdministrationHelmRepoUrl - env.ADMIN_CLIENT_IMAGE_TAG = userVersions.AdminClientImageTag - env.ADMIN_CLIENT_HELM_CHART_VERSION = userVersions.AdminClientHelmChartVersion - env.ADMIN_CLIENT_HELM_REPO_URL = userVersions.AdminClientHelmRepoUrl - env.VORGANG_MANAGER_IMAGE_TAG = userVersions.VorgangManagerImageTag - env.VORGANG_MANAGER_HELM_CHART_VERSION = userVersions.VorgangManagerHelmChartVersion - env.VORGANG_MANAGER_HELM_REPO_URL = userVersions.VorgangManagerHelmRepoUrl - env.USER_MANAGER_IMAGE_TAG = userVersions.UserManagerImageTag - env.USER_MANAGER_HELM_CHART_VERSION = userVersions.UserManagerHelmChartVersion - env.USER_MANAGER_HELM_REPO_URL = userVersions.UserManagerHelmRepoUrl + env.ALFA_IMAGE_TAG = userVersions.AlfaImageTag + env.ALFA_CLIENT_IMAGE_TAG = userVersions.AlfaClientImageTag + env.ALFA_HELM_CHART_VERSION = userVersions.AlfaHelmChartVersion + env.ALFA_HELM_REPO_URL = userVersions.AlfaHelmRepoUrl + env.ADMINISTRATION_IMAGE_TAG = userVersions.AdministrationImageTag + env.ADMINISTRATION_HELM_CHART_VERSION = userVersions.AdministrationHelmChartVersion + env.ADMINISTRATION_HELM_REPO_URL = userVersions.AdministrationHelmRepoUrl + env.ADMIN_CLIENT_IMAGE_TAG = userVersions.AdminClientImageTag + env.ADMIN_CLIENT_HELM_CHART_VERSION = userVersions.AdminClientHelmChartVersion + env.ADMIN_CLIENT_HELM_REPO_URL = userVersions.AdminClientHelmRepoUrl + env.VORGANG_MANAGER_IMAGE_TAG = userVersions.VorgangManagerImageTag + env.VORGANG_MANAGER_HELM_CHART_VERSION = userVersions.VorgangManagerHelmChartVersion + env.VORGANG_MANAGER_HELM_REPO_URL = userVersions.VorgangManagerHelmRepoUrl + env.USER_MANAGER_IMAGE_TAG = userVersions.UserManagerImageTag + env.USER_MANAGER_HELM_CHART_VERSION = userVersions.UserManagerHelmChartVersion + env.USER_MANAGER_HELM_REPO_URL = userVersions.UserManagerHelmRepoUrl } Void pushGitopsRepo() { - withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) { - dir('gitops') { - if (hasUnpushedCommits()) { - sh 'git config pull.rebase false' - sh 'git pull' - sh 'git push https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git' - } - } + withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) { + dir('gitops') { + if (hasUnpushedCommits()) { + sh 'git config pull.rebase false' + sh 'git pull' + sh 'git push https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git' + } } + } } Boolean hasUnpushedCommits() { - return sh (script: "git cherry -v | grep .", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer + return sh(script: "git cherry -v | grep .", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer } Void checkoutGitopsE2eBranch() { - dir('gitops') { - sh 'git checkout e2e' - } + dir('gitops') { + sh 'git checkout e2e' + } } Void generateNamespaces() { @@ -556,7 +563,7 @@ Void generateAdminNamespaceYaml() { envValues.ozgcloud.bezeichner = bezeichner envValues.administration.put("image", ['tag': env.ADMINISTRATION_IMAGE_TAG]) envValues.administration.put("helm", ['version': env.ADMINISTRATION_HELM_CHART_VERSION, 'repoUrl': env.ADMINISTRATION_HELM_REPO_URL]) - envValues.administration.put("ozgcloud", ['feature': ['organisationsEinheiten' : "true"], 'organisationEinheit': ['zufiSearchUri': generateZufiSearchUri(bezeichner)]]) + envValues.administration.put("ozgcloud", ['feature': ['organisationsEinheiten': "true"], 'organisationEinheit': ['zufiSearchUri': generateZufiSearchUri(bezeichner)]]) envValues.admin_client.put("image", ['tag': env.ADMIN_CLIENT_IMAGE_TAG]) envValues.admin_client.put("helm", ['version': env.ADMIN_CLIENT_HELM_CHART_VERSION, 'repoUrl': env.ADMIN_CLIENT_HELM_REPO_URL]) @@ -565,36 +572,39 @@ Void generateAdminNamespaceYaml() { } String generateZufiSearchUri(String bezeichner) { - return "https://${bezeichner}.dev.by.ozg-cloud.de/api/organisationseinheits?searchBy={searchBy}" + return "https://${bezeichner}.dev.by.ozg-cloud.de/api/organisationseinheits?searchBy={searchBy}" } String generateEaNamespaceYaml() { - return generateNamespaceYaml(env.EA_BEZEICHNER, "by-ea-dev.yaml"); + return generateNamespaceYaml(env.EA_BEZEICHNER, "by-ea-dev.yaml"); } String generateMainNamespaceYaml() { - return generateNamespaceYaml(env.MAIN_BEZEICHNER, "by-main-dev.yaml"); + return generateNamespaceYaml(env.MAIN_BEZEICHNER, "by-main-dev.yaml"); } String generateNamespaceYaml(String bezeichner, String valuesPathSuffix) { - def envValues - dir('alfa-client/apps/alfa-e2e/') { - envValues = readYaml file: "src/fixtures/argocd/" + valuesPathSuffix; + def envValues + dir('alfa-client/apps/alfa-e2e/') { + envValues = readYaml file: "src/fixtures/argocd/" + valuesPathSuffix; + + envValues.ozgcloud.bezeichner = bezeichner + envValues.alfa.put("image", ['tag': env.ALFA_IMAGE_TAG]) + envValues.alfa.put("helm", ['version': env.ALFA_HELM_CHART_VERSION, 'repoUrl': env.ALFA_HELM_REPO_URL]) - envValues.ozgcloud.bezeichner = bezeichner - envValues.alfa.put("image", ['tag': env.ALFA_IMAGE_TAG]) - envValues.alfa.put("helm", ['version': env.ALFA_HELM_CHART_VERSION, 'repoUrl': env.ALFA_HELM_REPO_URL]) + envValues.alfa_client.put("image", ['tag': env.ALFA_CLIENT_IMAGE_TAG]) + envValues.alfa_client.put("helm", ['version': env.ALFA_CLIENT_HELM_CHART_VERSION, 'repoUrl': env.ALFA_CLIENT_HELM_REPO_URL]) - envValues.vorgang_manager.put("image", ['tag': env.VORGANG_MANAGER_IMAGE_TAG]) - envValues.vorgang_manager.put("helm", ['version': env.VORGANG_MANAGER_HELM_CHART_VERSION, 'repoUrl': env.VORGANG_MANAGER_HELM_REPO_URL]) + envValues.vorgang_manager.put("image", ['tag': env.VORGANG_MANAGER_IMAGE_TAG]) + envValues.vorgang_manager.put("helm", ['version': env.VORGANG_MANAGER_HELM_CHART_VERSION, 'repoUrl': env.VORGANG_MANAGER_HELM_REPO_URL]) - envValues.user_manager.put("image", ['tag': env.USER_MANAGER_IMAGE_TAG]) - envValues.user_manager.put("helm", ['version': env.USER_MANAGER_HELM_CHART_VERSION, 'repoUrl': env.USER_MANAGER_HELM_REPO_URL]) - } + envValues.user_manager.put("image", ['tag': env.USER_MANAGER_IMAGE_TAG]) + envValues.user_manager.put("helm", ['version': env.USER_MANAGER_HELM_CHART_VERSION, 'repoUrl': env.USER_MANAGER_HELM_REPO_URL]) + } return writeYamlToGitOps(bezeichner, envValues); } -String writeYamlToGitOps(String bezeichner, Object envValues){ +String writeYamlToGitOps(String bezeichner, Object envValues) { def bezeichnerYaml = "dev/namespace/namespaces/by-${bezeichner}-dev.yaml" writeYaml file: "gitops/${bezeichnerYaml}", data: envValues, overwrite: true @@ -605,43 +615,43 @@ String writeYamlToGitOps(String bezeichner, Object envValues){ } Void deleteNamespaces(ozgCloudBezeichner) { - for(bezeichner in ozgCloudBezeichner) { - if (hasNamespaceFile(bezeichner)) { - removeNamespaceFile(bezeichner) - } + for (bezeichner in ozgCloudBezeichner) { + if (hasNamespaceFile(bezeichner)) { + removeNamespaceFile(bezeichner) } + } - pushGitopsRepo() + pushGitopsRepo() - for(bezeichner in ozgCloudBezeichner) { - waitForDeletion(bezeichner) - } + for (bezeichner in ozgCloudBezeichner) { + waitForDeletion(bezeichner) + } } Void removeNamespaceFile(String bezeichner) { - dir("gitops/dev/namespace/namespaces") { - sh "rm by-${bezeichner}-dev.yaml" - sh "git add by-${bezeichner}-dev.yaml" - sh "git commit -m 'delete e2e by-${bezeichner}-dev.yaml'" - } + dir("gitops/dev/namespace/namespaces") { + sh "rm by-${bezeichner}-dev.yaml" + sh "git add by-${bezeichner}-dev.yaml" + sh "git commit -m 'delete e2e by-${bezeichner}-dev.yaml'" + } } Boolean hasNamespaceFile(String bezeichner) { - return sh (script: "ls gitops/dev/namespace/namespaces | grep 'by-${bezeichner}-dev.yaml'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer + return sh(script: "ls gitops/dev/namespace/namespaces | grep 'by-${bezeichner}-dev.yaml'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer } Void waitForDeletion(String bezeichner) { - try { - sh "kubectl wait --for=delete applications/by-${bezeichner}-dev-application -n argocd --timeout=900s" - } catch (Exception e) { - error("Application by-${bezeichner}-dev-application konnte nicht gelöscht werden") - } + try { + sh "kubectl wait --for=delete applications/by-${bezeichner}-dev-application -n argocd --timeout=900s" + } catch (Exception e) { + error("Application by-${bezeichner}-dev-application konnte nicht gelöscht werden") + } } Void waitForAlfaRollout(ozgCloudBezeichner) { - for(bezeichner in ozgCloudBezeichner) { - waitForAlfaRollout(bezeichner) - } + for (bezeichner in ozgCloudBezeichner) { + waitForAlfaRollout(bezeichner) + } } Void waitForAdminRollout(String bezeichner) { @@ -651,41 +661,41 @@ Void waitForAdminRollout(String bezeichner) { } Void waitForAlfaRollout(String bezeichner) { - waitForHealthyApplication(bezeichner, 'application') - waitForHealthyApplication(bezeichner, 'vorgang-manager') - waitForHealthyApplication(bezeichner, 'user-manager') - waitForHealthyApplication(bezeichner, 'alfa') + waitForHealthyApplication(bezeichner, 'application') + waitForHealthyApplication(bezeichner, 'vorgang-manager') + waitForHealthyApplication(bezeichner, 'user-manager') + waitForHealthyApplication(bezeichner, 'alfa') } Void waitForHealthyApplication(String bezeichner, String application) { - try { - def countRetry = 0 - def maxRetry = 24 + try { + def countRetry = 0 + def maxRetry = 24 - while (!isApplicationPresent(bezeichner, application) && countRetry < maxRetry ) { - countRetry++ - sh "sleep 20" - } + while (!isApplicationPresent(bezeichner, application) && countRetry < maxRetry) { + countRetry++ + sh "sleep 20" + } - if (!isApplicationHealthy(bezeichner, application)) { - waitForHealthyStatus(bezeichner, application) - } - } catch (Exception e) { - echo "waitForHealthyApplication Exception: ${e}" - error("Application ${application} unhealthy") + if (!isApplicationHealthy(bezeichner, application)) { + waitForHealthyStatus(bezeichner, application) } + } catch (Exception e) { + echo "waitForHealthyApplication Exception: ${e}" + error("Application ${application} unhealthy") + } } Boolean isApplicationPresent(String bezeichner, String application) { - return sh (script: "kubectl get applications -n argocd | grep 'by-${bezeichner}-dev-${application}'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer + return sh(script: "kubectl get applications -n argocd | grep 'by-${bezeichner}-dev-${application}'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer } Boolean isApplicationHealthy(String bezeichner, String application) { - return sh (script: "kubectl get application/by-${bezeichner}-dev-${application} -n argocd -o=jsonpath='{.status.health.status}' | grep Healthy", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer + return sh(script: "kubectl get application/by-${bezeichner}-dev-${application} -n argocd -o=jsonpath='{.status.health.status}' | grep Healthy", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer } Void waitForHealthyStatus(String bezeichner, String application) { - sh "kubectl wait --for=jsonpath='{.status.health.status}'=Healthy applications/by-${bezeichner}-dev-${application} -n argocd --timeout=900s" + sh "kubectl wait --for=jsonpath='{.status.health.status}'=Healthy applications/by-${bezeichner}-dev-${application} -n argocd --timeout=900s" } Void publishAlfaE2ETestResult(String appVariant, String reportName) { @@ -697,16 +707,16 @@ Void publishAdminE2ETestResult() { } Void publishE2ETestResult(String appName, String appVariant, String reportName) { - def reportDir = "alfa-client/apps/"+appName+"/reports/"+appVariant; + def reportDir = "alfa-client/apps/" + appName + "/reports/" + appVariant; - publishHTML ( + publishHTML( target: [ - allowMissing: false, + allowMissing : false, alwaysLinkToLastBuild: false, - keepAll: true, - reportDir: reportDir, - reportFiles: 'report.html', - reportName: reportName + keepAll : true, + reportDir : reportDir, + reportFiles : 'report.html', + reportName : reportName ] ) } @@ -714,7 +724,7 @@ Void publishE2ETestResult(String appName, String appVariant, String reportName) String runTests(String bezeichner, String appName, String appVariant, String keycloakClientName, Integer dbPort, String stageName) { def config = generateCypressConfig(bezeichner, appName, appVariant, keycloakClientName, dbPort) try { - dir('alfa-client'){ + dir('alfa-client') { sh "pnpm run cypress:version" sh "apps/run-tests.sh ${appName} ${appVariant} ${config}" } @@ -726,38 +736,37 @@ String runTests(String bezeichner, String appName, String appVariant, String key } Void printNpmDebugLog() { - if (hasNpmDebugLog()) { - sh "cat pnpm-debug.log" - } - else { - echo "pnpm debug log not found" - } + if (hasNpmDebugLog()) { + sh "cat pnpm-debug.log" + } else { + echo "pnpm debug log not found" + } } String makeUrlConform(String input) { - return input.replaceAll(/[^a-zA-Z0-9]+/, "").toLowerCase() + return input.replaceAll(/[^a-zA-Z0-9]+/, "").toLowerCase() } String generateBezeichner(String stage) { - def branchName = makeUrlConform(env.BRANCH_NAME) - def stageName = makeUrlConform(stage) + def branchName = makeUrlConform(env.BRANCH_NAME) + def stageName = makeUrlConform(stage) - return "${cutBranchNameForKeycloakRealm(branchName, stageName)}-${stageName}" + return "${cutBranchNameForKeycloakRealm(branchName, stageName)}-${stageName}" } String cutBranchNameForKeycloakRealm(String branchName, String stageName) { - final maxKeycloakRealmLength = 30 + final maxKeycloakRealmLength = 30 - def cutBranchNamePosition = maxKeycloakRealmLength - (stageName.length() + "${env.BUNDESLAND}---dev".length()) + def cutBranchNamePosition = maxKeycloakRealmLength - (stageName.length() + "${env.BUNDESLAND}---dev".length()) - return branchName.take(cutBranchNamePosition) + return branchName.take(cutBranchNamePosition) } String generateCypressConfig(String bezeichner, String appName, String appVariant, String keycloakClientName, Integer dbPort) { def namespace = generateNamespace(bezeichner) - def configName = "cypress-ci-"+appVariant+".json" + def configName = "cypress-ci-" + appVariant + ".json" - dir("alfa-client/apps/${appName}/"){ + dir("alfa-client/apps/${appName}/") { def config = readJSON file: 'cypress-ci.json' def vorgangManagerDatabaseSecret = getVorgangManagerDatabaseSecret(namespace); @@ -784,155 +793,154 @@ String generateCypressConfig(String bezeichner, String appName, String appVarian sh "cat ${configName}" } - return "cypress-ci-"+appVariant+".config.ts" + return "cypress-ci-" + appVariant + ".config.ts" } -String generateUrlBezeichner(String bezeichner, String appName){ - if(appName == 'admin-e2e'){ - return "${bezeichner}-admin"; - } - return bezeichner; +String generateUrlBezeichner(String bezeichner, String appName) { + if (appName == 'admin-e2e') { + return "${bezeichner}-admin"; + } + return bezeichner; } - String makePasswordUrlConform(String password) { - return sh (script: "printf %s ${password} | jq -sRr @uri", returnStdout: true); + return sh(script: "printf %s ${password} | jq -sRr @uri", returnStdout: true); } Map getVorgangManagerDatabaseSecret(String namespace) { - return readJSON ( text: sh (script: "kubectl get secret ozg-mongodb-admin-vorgang-manager-user -n ${namespace} -o jsonpath={.data}", returnStdout: true)) + return readJSON(text: sh(script: "kubectl get secret ozg-mongodb-admin-vorgang-manager-user -n ${namespace} -o jsonpath={.data}", returnStdout: true)) } -Map getUserManagerEnv(String namespace, dbPort){ - def userManagerDatabaseSecret = getUserManagerDatabaseSecret(namespace); - def decodedPassword = decodeString(userManagerDatabaseSecret.password); - def parsablePassword = makePasswordUrlConform(decodedPassword); +Map getUserManagerEnv(String namespace, dbPort) { + def userManagerDatabaseSecret = getUserManagerDatabaseSecret(namespace); + def decodedPassword = decodeString(userManagerDatabaseSecret.password); + def parsablePassword = makePasswordUrlConform(decodedPassword); - return [ - "dbUrl": "mongodb://${decodeString(userManagerDatabaseSecret.username)}:${parsablePassword}@localhost:${dbPort}/admin?ssl=false&directConnection=true&socketTimeoutMS=30000&heartbeatFrequencyMS=10000" as String, - "database": "user-manager-database" - ] + return [ + "dbUrl" : "mongodb://${decodeString(userManagerDatabaseSecret.username)}:${parsablePassword}@localhost:${dbPort}/admin?ssl=false&directConnection=true&socketTimeoutMS=30000&heartbeatFrequencyMS=10000" as String, + "database": "user-manager-database" + ] } Map getUserManagerDatabaseSecret(String namespace) { - return readJSON ( text: sh (script: "kubectl get secret ozg-mongodb-admin-user-manager-user -n ${namespace} -o jsonpath={.data}", returnStdout: true)) + return readJSON(text: sh(script: "kubectl get secret ozg-mongodb-admin-user-manager-user -n ${namespace} -o jsonpath={.data}", returnStdout: true)) } Void sendFailureMessage() { - def data = [ - "msgtype": "m.text", - "body": "E2E-Tests: Failed stage: ${getFailedStage()} Build-ID: ${env.BUILD_NUMBER} Link: ${JENKINS_URL}" as String, - "format": "org.matrix.custom.html", - "formatted_body": "E2E-Tests: Failed stage: ${getFailedStage()} Build-ID: <a href='${JENKINS_URL}'>${env.BUILD_NUMBER}</a>" as String - ] + def data = [ + "msgtype" : "m.text", + "body" : "E2E-Tests: Failed stage: ${getFailedStage()} Build-ID: ${env.BUILD_NUMBER} Link: ${JENKINS_URL}" as String, + "format" : "org.matrix.custom.html", + "formatted_body": "E2E-Tests: Failed stage: ${getFailedStage()} Build-ID: <a href='${JENKINS_URL}'>${env.BUILD_NUMBER}</a>" as String + ] - sh "curl -XPOST -H 'authorization: Bearer ${getElementAccessToken()}' -d '${JsonOutput.toJson(data)}' https://matrix.ozg-sh.de/_matrix/client/v3/rooms/${getElementRoomId()}/send/m.room.message" + sh "curl -XPOST -H 'authorization: Bearer ${getElementAccessToken()}' -d '${JsonOutput.toJson(data)}' https://matrix.ozg-sh.de/_matrix/client/v3/rooms/${getElementRoomId()}/send/m.room.message" } String getFailedStage() { - if (FAILED_PARALLEL_STAGE.trim()) { - return FAILED_PARALLEL_STAGE.trim() - } + if (FAILED_PARALLEL_STAGE.trim()) { + return FAILED_PARALLEL_STAGE.trim() + } - return FAILED_STAGE + return FAILED_STAGE } String getElementRoomId() { - final releaseRoomId = "!oWZpUGTFsxkJIYNfYg:matrix.ozg-sh.de" - final masterRoomId = "!iQPAvQIiRwRpNOszjw:matrix.ozg-sh.de" + final releaseRoomId = "!oWZpUGTFsxkJIYNfYg:matrix.ozg-sh.de" + final masterRoomId = "!iQPAvQIiRwRpNOszjw:matrix.ozg-sh.de" - if (isReleaseBranch()) { - return releaseRoomId - } + if (isReleaseBranch()) { + return releaseRoomId + } - return masterRoomId + return masterRoomId } Boolean isReleaseBranch() { - return env.BRANCH_NAME == 'release' + return env.BRANCH_NAME == 'release' } String getElementAccessToken() { - withCredentials([string(credentialsId: 'element-login-json', variable: 'LOGIN_JSON')]) { - return readJSON ( text: sh (script: '''curl -XPOST -d \"$LOGIN_JSON\" https://matrix.ozg-sh.de/_matrix/client/v3/login''', returnStdout: true)).access_token - } + withCredentials([string(credentialsId: 'element-login-json', variable: 'LOGIN_JSON')]) { + return readJSON(text: sh(script: '''curl -XPOST -d \"$LOGIN_JSON\" https://matrix.ozg-sh.de/_matrix/client/v3/login''', returnStdout: true)).access_token + } } Map getElasticsearchEnv(String namespace) { - def elasticsearchSecret = getElasticsearchSecret(namespace) + def elasticsearchSecret = getElasticsearchSecret(namespace) - return [ - "user": decodeString(elasticsearchSecret.username), - "password": decodeString(elasticsearchSecret.password), - "index": decodeString(elasticsearchSecret.index), - "url": "https://localhost:9200" - ] + return [ + "user" : decodeString(elasticsearchSecret.username), + "password": decodeString(elasticsearchSecret.password), + "index" : decodeString(elasticsearchSecret.index), + "url" : "https://localhost:9200" + ] } Void forwardMongoDbPort(String namespace) { - try { - def minPort = 20000 - def portRange = 20000 - //def dbPort = "${Math.abs(new Random().nextInt(portRange+1))+minPort}" as Integer - def dbPort = Math.abs(new Random().nextInt(portRange+1))+minPort + try { + def minPort = 20000 + def portRange = 20000 + //def dbPort = "${Math.abs(new Random().nextInt(portRange+1))+minPort}" as Integer + def dbPort = Math.abs(new Random().nextInt(portRange + 1)) + minPort - echo "Forwarding MongoDB Port to local port ${dbPort}" + echo "Forwarding MongoDB Port to local port ${dbPort}" - def pidFile = generateMongoDbPortForwardPidFile(namespace) + def pidFile = generateMongoDbPortForwardPidFile(namespace) - sh "kubectl port-forward ozg-mongodb-0 ${dbPort}:27017 -n ${namespace} & echo \$! > ${pidFile}" + sh "kubectl port-forward ozg-mongodb-0 ${dbPort}:27017 -n ${namespace} & echo \$! > ${pidFile}" - sh "sleep 20" + sh "sleep 20" - return dbPort - } - catch (Exception e) { - echo "forwardMongoDbPort Exception: ${e}" - error("Error forwarding service") - } + return dbPort + } + catch (Exception e) { + echo "forwardMongoDbPort Exception: ${e}" + error("Error forwarding service") + } } Void stopForwardMongoDbPort(String namespace) { - echo "Removing forwarding MongoDB Port to local port for ${namespace}" - def pidFile = generateMongoDbPortForwardPidFile(namespace) - dir('alfa-client/apps/alfa-e2e/src/jenkins-build-files') { - sh "./kill-pid-by-filename.sh ${pidFile}" - } + echo "Removing forwarding MongoDB Port to local port for ${namespace}" + def pidFile = generateMongoDbPortForwardPidFile(namespace) + dir('alfa-client/apps/alfa-e2e/src/jenkins-build-files') { + sh "./kill-pid-by-filename.sh ${pidFile}" + } } Void generateMongoDbPortForwardPidFile(String namespace) { - return "/tmp/pid_file_mongodb_port_forward_${namespace}.pid" + return "/tmp/pid_file_mongodb_port_forward_${namespace}.pid" } Void forwardElasticSearch() { - if(!isElasticSearchForwarded()) { - sh "kubectl port-forward ozg-search-cluster-es-ozg-search-0 9200:9200 -n elastic-system &" - } + if (!isElasticSearchForwarded()) { + sh "kubectl port-forward ozg-search-cluster-es-ozg-search-0 9200:9200 -n elastic-system &" + } } Boolean isElasticSearchForwarded() { - return sh (script: "lsof -i -P -n | grep LISTEN | grep :9200", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer + return sh(script: "lsof -i -P -n | grep LISTEN | grep :9200", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer } String generateNamespace(String bezeichner) { - return "${env.BUNDESLAND}-${bezeichner}-dev" + return "${env.BUNDESLAND}-${bezeichner}-dev" } String decodeString(String encoded) { - return sh (script: "echo -n ${encoded} | base64 --decode", returnStdout: true) + return sh(script: "echo -n ${encoded} | base64 --decode", returnStdout: true) } Map getElasticsearchSecret(String namespace) { - return readJSON ( text: sh (script: "kubectl get secret elasticsearch-credentials -n ${namespace} -o jsonpath={.data}", returnStdout: true)) + return readJSON(text: sh(script: "kubectl get secret elasticsearch-credentials -n ${namespace} -o jsonpath={.data}", returnStdout: true)) } Boolean hasNpmDebugLog() { - return sh (script: "ls -l pnpm-debug.log", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer + return sh(script: "ls -l pnpm-debug.log", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer } Map getSmockerEnv(String namespace) { - return [ - "url": "https://${namespace}-smocker.dev.by.ozg-cloud.de".toString() - ] + return [ + "url": "https://${namespace}-smocker.dev.by.ozg-cloud.de".toString() + ] } \ No newline at end of file diff --git a/alfa-client/apps/admin-e2e/docker-compose.yml b/alfa-client/apps/admin-e2e/docker-compose.yml index 11682ebeb086ca5740d4da1b5c3c2feaa34aaee1..edc5dc8b2f98963025b2521c960a87a29efc6efb 100644 --- a/alfa-client/apps/admin-e2e/docker-compose.yml +++ b/alfa-client/apps/admin-e2e/docker-compose.yml @@ -79,7 +79,7 @@ services: depends_on: - user-manager extra_hosts: - - "host.docker.internal:host-gateway" + - 'host.docker.internal:host-gateway' alfa-cors-proxy: image: alfa-cors-proxy @@ -91,7 +91,6 @@ services: alfa: condition: service_started - user-manager: image: docker.ozg-sh.de/user-manager:${USER_MANAGER_DOCKER_IMAGE:-snapshot-latest} platform: linux/amd64 @@ -145,4 +144,4 @@ services: user-manager: condition: service_started extra_hosts: - - "host.docker.internal:host-gateway" + - 'host.docker.internal:host-gateway' diff --git a/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts index 0af0f39b14cfd07cdd0ad868b8c780c1bbbea19f..673ea2c47571873cc69795547f6dc5359609e99a 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 @@ -22,154 +22,187 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import 'cypress-real-events'; -import { AdminUserE2E } from '../../model/util'; -import { exist } from '../../support/cypress.util'; +import { convertToDataTestId } from '../../support/tech-util'; -export class BenutzerE2EComponent { +//TODO BenutzerListPage erstellen welche den Button und die Liste enthaelt. +export class BenutzerListE2EComponent { + private readonly headline: string = 'user-list-headline'; + private readonly list: string = 'user-list'; private readonly benutzerHinzufuegenButton: string = 'add-user-button'; - 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 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'; - private readonly saveButton: string = 'save-button'; - private readonly organisationsEinheitCheckbox: string = '-checkbox-editor'; + + public getHeadline(): Cypress.Chainable<Element> { + return cy.getTestElement(this.headline); + } + + public getList(): Cypress.Chainable<Element> { + return cy.getTestElement(this.list); + } public getHinzufuegenButton(): Cypress.Chainable<Element> { return cy.getTestElement(this.benutzerHinzufuegenButton); } - public hinzufuegenButtonIsVisible(): void { - exist(this.getHinzufuegenButton()); + public getItem(userName: string): BenutzerListItemE2EComponent { + return new BenutzerListItemE2EComponent(userName); } +} + +export class BenutzerListItemE2EComponent { + private root: string; + + private fullName: string = 'fullname'; + private email: string = 'email'; + private userName: string = 'username'; + + private roles: string = 'roles'; - public clickAddUser(): void { - this.getHinzufuegenButton().click(); + private organisationsEinheiten: string = 'organisations-einheiten'; + private noOrganisationsEinheitenText: string = 'no-organisations-einheit-text'; + + constructor(userName: string) { + this.root = convertToDataTestId(userName) + '-user-entry'; } - public addUser(user: AdminUserE2E): void { - this.enterVorname(user.vorname); - this.enterNachname(user.nachname); - this.enterBenutzername(user.benutzername); - this.enterMail(user.email); + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); + } - if (user.isAdmin) { - this.clickAdminCheckbox(); - } - if (user.isUser) { - this.clickUserCheckbox(); - } - if (user.isLoeschen) { - this.clickLoeschenCheckbox(); - } - if (user.isPoststelle) { - this.clickPostCheckbox(); - } + public getRoles(): Cypress.Chainable<Element> { + return this.getRoot().getTestElementWithClass(this.roles); + } - if (user.organisationseinheiten) { - for (const einheit of user.organisationseinheiten) { - this.clickOrganisationsEinheitCheckbox(einheit); - } - } - this.saveUser(); + public getOrganisationsEinheiten(): Cypress.Chainable<Element> { + return this.getRoot().getTestElementWithClass(this.organisationsEinheiten); } - public getUserEntry(user: string): Cypress.Chainable<Element> { - user = this.userEntry + user; - return cy.getTestElement(user); + public getNoOrganisationsEinheitText(): Cypress.Chainable<Element> { + return this.getRoot().getTestElementWithClass(this.noOrganisationsEinheitenText); } - public clickUserEntry(user: string): void { - this.getUserEntry(user).click(); + public getFullName(): Cypress.Chainable<Element> { + return this.getRoot().getTestElementWithClass(this.fullName); } - public stringExistsInUserEntry(phrase: string, user: string): void { - this.getUserEntry(user).within(() => { - exist(cy.contains(phrase)); - }); + public getEMail(): Cypress.Chainable<Element> { + return this.getRoot().getTestElementWithClass(this.email); } - public getVornameInput(): Cypress.Chainable<Element> { - return cy.getTestElement(this.userVorname); + public getUserName(): Cypress.Chainable<Element> { + return this.getRoot().getTestElementWithClass(this.userName); + } +} + +export class BenutzerE2EComponent { + private readonly headline: string = 'benutzer-form-headline'; + + 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 adminCheckboxLabel: string = 'Admin'; + private readonly loeschenCheckboxLabel: string = 'Löschen'; + private readonly userCheckboxLabel: string = 'User'; + private readonly postCheckboxLabel: string = 'Poststelle'; + private readonly datenbeauftragungLabel: string = 'Datenbeauftragung'; + + private readonly organisationsEinheitCheckboxSuffix: string = '-checkbox-editor'; + + private readonly saveButton: string = 'save-button'; + private readonly deleteButton: string = 'delete-button'; + + public getHeadline(): Cypress.Chainable<Element> { + return cy.getTestElement(this.headline); } - public enterVorname(vorname: string): void { - this.getVornameInput().type(vorname); + public getHeadline(): Cypress.Chainable<Element> { + return cy.getTestElement(this.headline); } - public getNachnameInput(): Cypress.Chainable<Element> { - return cy.getTestElement(this.userNachname); + public getVornameInput(): Cypress.Chainable<Element> { + return cy.getTestElement(this.userVorname); } - public enterNachname(nachname: string): void { - this.getNachnameInput().type(nachname); + public getNachnameInput(): Cypress.Chainable<Element> { + return cy.getTestElement(this.userNachname); } public getBenutzernameInput(): Cypress.Chainable<Element> { return cy.getTestElement(this.userBenutzername); } - public enterBenutzername(benutzername: string): void { - this.getBenutzernameInput().type(benutzername); - } - public getMailInput(): Cypress.Chainable<Element> { return cy.getTestElement(this.userMail); } - public enterMail(mail: string): void { - this.getMailInput().type(mail); + public getAdminCheckbox(): BenutzerCheckboxE2EComponent { + return new BenutzerCheckboxE2EComponent(this.adminCheckboxLabel); } - public getAdminCheckbox(): Cypress.Chainable<Element> { - return cy.getTestElement(this.adminCheckbox); + public getLoeschenCheckbox(): BenutzerCheckboxE2EComponent { + return new BenutzerCheckboxE2EComponent(this.loeschenCheckboxLabel); } - public clickAdminCheckbox(): void { - this.getAdminCheckbox().click(); + public getUserCheckbox(): BenutzerCheckboxE2EComponent { + return new BenutzerCheckboxE2EComponent(this.userCheckboxLabel); } - public getLoeschenCheckbox() { - return cy.getTestElement(this.loeschenCheckbox); + public getPostCheckbox(): BenutzerCheckboxE2EComponent { + return new BenutzerCheckboxE2EComponent(this.postCheckboxLabel); } - public clickLoeschenCheckbox(): void { - this.getLoeschenCheckbox().click(); + public getDatenbeauftragungCheckbox(): BenutzerCheckboxE2EComponent { + return new BenutzerCheckboxE2EComponent(this.datenbeauftragungLabel); } - public getUserCheckbox(): Cypress.Chainable<Element> { - return cy.getTestElement(this.userCheckbox); + public getOrganisationsEinheitCheckbox(einheit: string): Cypress.Chainable<Element> { + return cy.getTestElement(einheit + this.organisationsEinheitCheckboxSuffix); } - public clickUserCheckbox(): void { - this.getUserCheckbox().click(); + public getSaveButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.saveButton); } - public getPostCheckbox(): Cypress.Chainable<Element> { - return cy.getTestElement(this.postCheckbox); + public getDeleteButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.deleteButton); } +} - public clickPostCheckbox(): void { - this.getPostCheckbox().click(); +export class BenutzerDeleteDialogE2EComponent { + private readonly deleteButton: string = 'dialog-delete'; + private readonly cancelButton: string = 'cancel-dialog'; + + public getCancelButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.cancelButton); } - public getSaveButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.saveButton); + public getDeleteButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.deleteButton); } +} + +export class BenutzerCheckboxE2EComponent { + private rootPrefix: string; + private prefix: string; + + private readonly adminCheckbox: string = '-checkbox-editor'; + private readonly adminInfoButtonSuffix: string = '-role-info-button'; + private readonly adminInfoButtonTooltipSuffix: string = '-role-info-button-tooltip'; - public saveUser(): void { - this.getSaveButton().click(); + constructor(label: string) { + this.rootPrefix = convertToDataTestId(label); + this.prefix = convertToDataTestId(label.toLocaleLowerCase()); } - public getOrganisationsEinheitCheckbox(einheit: string): Cypress.Chainable<Element> { - return cy.getTestElement(einheit + this.organisationsEinheitCheckbox); + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(`${this.rootPrefix}${this.adminCheckbox}`); + } + + public getInfoButton(): Cypress.Chainable<Element> { + return cy.getTestElement(`${this.prefix}${this.adminInfoButtonSuffix}`); } - public clickOrganisationsEinheitCheckbox(einheit: string): void { - this.getOrganisationsEinheitCheckbox(einheit).click(); + public getInfoButtonTooltip(): Cypress.Chainable<Element> { + return cy.getTestElement(`${this.prefix}${this.adminInfoButtonTooltipSuffix}`); } } diff --git a/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten-signatur.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten-signatur.e2e.component.ts deleted file mode 100644 index e1f91229ad68813bbab4625854ddc2ed6f89b79d..0000000000000000000000000000000000000000 --- a/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten-signatur.e2e.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { clearText, haveValue, typeText } from '../../support/cypress.util'; - -export class OrganisationseinheitenSignaturE2EComponent { - private readonly organisationsEinheitName: string = 'organisations-form-container-headline'; - private readonly signatureText: string = 'signature-textarea'; - private readonly saveSignatureButton: string = 'save-button'; - - public getOrganisationsEinheitName(): Cypress.Chainable<Element> { - return cy.getTestElement(this.organisationsEinheitName); - } - - public getSignatureText(): Cypress.Chainable<Element> { - return cy.getTestElement(this.signatureText); - } - - public setSignature(signatur: string): void { - this.clearSignature(); - typeText(this.getSignatureText(), signatur); - } - - public clearSignature(): void { - clearText(this.getSignatureText()); - } - - public getSaveButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.saveSignatureButton); - } - - public saveSignature(): void { - this.getSaveButton().click(); - } - - public hasSignature(compare: string): void { - haveValue(this.getSignatureText(), compare); - } - - public hasScrollbar(): void { - this.getSignatureText().then((textarea) => { - const scrollHeight = textarea[0].scrollHeight; - const clientHeight = textarea[0].clientHeight; - - expect(scrollHeight).to.be.greaterThan(clientHeight); - }); - } -} diff --git a/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component.ts index 4d38f761c34b586f7da999a6a2e46a1d74056371..ac3917c2dd21ca7859c009a824267033c8df5349 100644 --- a/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component.ts +++ b/alfa-client/apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component.ts @@ -21,66 +21,52 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { containClass, exist, haveText, notContainClass } from '../../support/cypress.util'; +export class OrganisationsEinheitListE2EComponent { + private readonly root: string = 'organisations-einheit-list'; -export class OrganisationsEinheitenE2EComponent { - private readonly organisationsEinheitenList: string = 'organisations-einheit-list'; - private readonly organisationsEinheitenName: string = 'organisations-einheit-name'; - private readonly organisationsEinheitenID: string = 'organisations-einheit-id'; - private readonly organisationsEinheitHinzufuegen: string = 'add-organisationseinheit-button'; - private readonly errorColor: string = 'text-red-500'; - private readonly errorIcon: string = 'organisations-einheit-sync-error'; - - private readonly errorText: string = 'Organisationseinheit wurde nicht in den PVOG-Daten gefunden.'; - - public getOrganisationsEinheitList(): Cypress.Chainable<Element> { - return cy.getTestElement(this.organisationsEinheitenList); + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); } - public getListItemByName(name: string): Cypress.Chainable<JQuery<HTMLElement>> { - return cy.getTestElement(this.organisationsEinheitenName).contains(name); + public getListItem(name: string): OrganisationsEinheitListItemE2EComponent { + return new OrganisationsEinheitListItemE2EComponent(name); } +} - public openOrganisationsEinheit(name: string): void { - this.getListItemByName(name).click(); - } +export class OrganisationsEinheitListItemE2EComponent { + private root: string; - public getOrganisationsEinheitHinzufuegenButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.organisationsEinheitHinzufuegen); - } + private readonly organisationsEinheitItemSuffix: string = '-organisation-item'; + private readonly deleteButton: string = 'delete-button'; - public clickHinzufuegen(): void { - this.getOrganisationsEinheitHinzufuegenButton().click(); + constructor(name: string) { + this.root = name; } - public organisationsEinheitContainsID(name: string, id: string): void { - this.getListItemByName(name) - .parents('a') - .within(() => { - haveText(cy.getTestElement(this.organisationsEinheitenID), id); - }); + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root + this.organisationsEinheitItemSuffix); } - public elementIsShownAsError(name: string): void { - containClass(this.getListItemByName(name).closest('ods-list-item'), this.errorColor); + public getDeleteButton(): Cypress.Chainable<Element> { + return this.getRoot().findTestElementWithClass(this.deleteButton); } +} + +export class OrganisationsEinheitDeleteDialogE2EComponent { + private readonly root: string = 'organisations-einheit-delete-dialog'; + + private readonly deleteButton: string = 'dialog-delete-button'; + private readonly cancelButton: string = 'dialog-cancel-button'; - public getErrorIconInElement(name: string): Cypress.Chainable<JQuery<HTMLAnchorElement>> { - return this.getListItemByName(name) - .parents('a') - .within(() => { - exist(cy.getTestElement(this.errorIcon)); - }); + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); } - public getErrorTooltip(name: string): Cypress.Chainable<JQuery<HTMLElement>> { - return this.getErrorIconInElement(name) - .find('ods-exclamation-icon[mattooltip]') - .should('have.attr', 'mattooltip') - .and('include', this.errorText); + public getDeleteButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.deleteButton); } - public elementShowsNoError(name: string): void { - notContainClass(this.getListItemByName(name).closest('ods-list-item'), this.errorColor); + public getCancelButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.cancelButton); } } diff --git a/alfa-client/apps/admin-e2e/src/components/postfach/postfach.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/postfach/postfach.e2e.component.ts index 62527711781b022092af5336db78779b0df2c914..f177cf791efec0113ceaebf9d6098e2008bc21f9 100644 --- a/alfa-client/apps/admin-e2e/src/components/postfach/postfach.e2e.component.ts +++ b/alfa-client/apps/admin-e2e/src/components/postfach/postfach.e2e.component.ts @@ -21,19 +21,24 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { haveValue, typeText } from '../../support/cypress.util'; +import { haveValue } from '../../support/cypress.util'; export class PostfachE2EComponent { + private readonly headline: string = 'headline'; private readonly signaturText: string = 'signature-textarea'; private readonly saveSignaturButton: string = 'save-button'; + public getHeadline(): any { + return cy.getTestElement(this.headline); + } + public getSignaturText(): any { return cy.getTestElement(this.signaturText); } public setSignatur(signatur: string): void { this.clearSignatur(); - typeText(this.getSignaturText(), signatur); + this.getSignaturText().type(signatur); } public clearSignatur(): void { diff --git a/alfa-client/apps/admin-e2e/src/components/statistik/statistik-fields-form.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/statistik/statistik-fields-form.e2e.component.ts index f3c565869006d9935a5c54de0a9fb388d73cbc3c..8936e02edbe69c765364304bc497d2be61a970da 100644 --- a/alfa-client/apps/admin-e2e/src/components/statistik/statistik-fields-form.e2e.component.ts +++ b/alfa-client/apps/admin-e2e/src/components/statistik/statistik-fields-form.e2e.component.ts @@ -1,15 +1,16 @@ import { enterWith } from '../../support/cypress.util'; export class StatistikFieldsFormE2EComponent { - private readonly locatorFormEngineInput: string = 'form-engine-input'; - private readonly locatorFormIdInput: string = 'form-id-input'; - private readonly locatorFormDataFieldInput: string = 'data-statistik-field-'; - private readonly locatorAddFieldButton: string = 'add-data-field-button'; - private readonly locatorSaveButton: string = 'save-statistik-fields-button'; - private readonly locatorCancelButton: string = 'cancel-statistik-fields-button'; + private readonly formEngineInput: string = 'form-engine-name-text-input'; + private readonly formIdInput: string = 'form-id-text-input'; + private readonly formDataFieldInput: string = 'mapping-field-'; + private readonly addDataFieldButton: string = 'add-mapping-button'; + private readonly deleteDataFieldButtonPrefix: string = 'remove-mapping-button-'; + private readonly saveButton: string = 'save-button'; + private readonly cancelButton: string = 'cancel-button'; public getFormEngineInput(): Cypress.Chainable<Element> { - return cy.getTestElement(this.locatorFormEngineInput); + return cy.getTestElement(this.formEngineInput); } public enterFormEngine(text: string): void { @@ -17,7 +18,7 @@ export class StatistikFieldsFormE2EComponent { } public getFormIdInput(): Cypress.Chainable<Element> { - return cy.getTestElement(this.locatorFormIdInput); + return cy.getTestElement(this.formIdInput); } public enterFormId(text: string): void { @@ -25,7 +26,7 @@ export class StatistikFieldsFormE2EComponent { } public getAddFieldButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.locatorAddFieldButton); + return cy.getTestElement(this.addDataFieldButton); } public addField(): void { @@ -33,15 +34,23 @@ export class StatistikFieldsFormE2EComponent { } public getDataFieldInput(index: number): Cypress.Chainable<Element> { - return cy.getTestElement(this.locatorFormDataFieldInput + index); + return cy.getTestElement(this.formDataFieldInput + index + '-text-input'); } public enterDataFieldPath(text: string, index: number): void { enterWith(this.getDataFieldInput(index), text); } + public getDataFieldDeleteButton(index: number): Cypress.Chainable<Element> { + return cy.getTestElement(this.deleteDataFieldButtonPrefix + index); + } + + public deleteDataField(index: number): void { + this.getDataFieldDeleteButton(index).click(); + } + public getSaveButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.locatorSaveButton); + return cy.getTestElement(this.saveButton); } public save(): void { @@ -49,7 +58,7 @@ export class StatistikFieldsFormE2EComponent { } public getCancelButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.locatorCancelButton); + return cy.getTestElement(this.cancelButton); } public cancel(): void { diff --git a/alfa-client/apps/admin-e2e/src/components/statistik/statistik.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/statistik/statistik.e2e.component.ts index 8748a5bad339ca0bc381d88d51c3fbe3747d0d16..b7467232f5d596a2984bc109e84180810c5e8dc3 100644 --- a/alfa-client/apps/admin-e2e/src/components/statistik/statistik.e2e.component.ts +++ b/alfa-client/apps/admin-e2e/src/components/statistik/statistik.e2e.component.ts @@ -1,14 +1,12 @@ -import { exist } from '../../support/cypress.util'; - export class StatistikE2EComponent { private readonly locatorHeaderText: string = 'statistik-header-text'; private readonly locatorWeitereFelderAuswertenButton = 'weitere-felder-auswerten-button'; - public isHeaderTextVisible(): void { - exist(cy.getTestElement(this.locatorHeaderText)); + public getHeaderText(): Cypress.Chainable<Element> { + return cy.getTestElement(this.locatorHeaderText); } - public getWeiterFelderAuswertenButton(): Cypress.Chainable<Element> { + public getWeitereFelderAuswertenButton(): Cypress.Chainable<Element> { return cy.getTestElement(this.locatorWeitereFelderAuswertenButton); } } diff --git a/alfa-client/apps/admin-e2e/src/components/user-profile/current-user-profile.component.e2e.ts b/alfa-client/apps/admin-e2e/src/components/user-profile/current-user-profile.component.e2e.ts index b96370d68b65cf7848e13b6eb5807473aadb40a3..effa3edb707cf24d98306dc972fefae6770b154e 100644 --- a/alfa-client/apps/admin-e2e/src/components/user-profile/current-user-profile.component.e2e.ts +++ b/alfa-client/apps/admin-e2e/src/components/user-profile/current-user-profile.component.e2e.ts @@ -26,6 +26,7 @@ import { UserProfileE2EComponent } from './user-profile.component.e2e'; export class CurrentUserProfileE2EComponent { private readonly locatorUserIconButton: string = 'popup-button-content'; private readonly locatorLogoutButton: string = 'popup-logout-button'; + private readonly locatorDocumentation: string = 'admin-documentation'; private readonly locatorRoot: string = 'current-user'; @@ -46,7 +47,11 @@ export class CurrentUserProfileE2EComponent { return cy.getTestElement(this.locatorUserIconButton); } - private getLogoutButton() { + public getLogoutButton() { return cy.getTestElement(this.locatorLogoutButton); } + + public getDocumentation() { + return cy.getTestElement(this.locatorDocumentation); + } } diff --git a/alfa-client/apps/admin-e2e/src/components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component.ts index 337cad0fe51c3ba1e7bf0b1123e8f324c57dbc73..6bd293d6fc2c39febbea6bfef0fb4e66de7e22b6 100644 --- a/alfa-client/apps/admin-e2e/src/components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component.ts +++ b/alfa-client/apps/admin-e2e/src/components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component.ts @@ -1,5 +1,3 @@ -import { typeText } from '../../support/cypress.util'; - export class ZustaendigeStelleDialogE2EComponent { private readonly locatorZustaendigeStelleForm: string = 'search-organisations-einheit'; private readonly locatorSearchInput: string = 'instant_search-text-input'; @@ -15,7 +13,7 @@ export class ZustaendigeStelleDialogE2EComponent { } public enterSearchTerm(searchTerm: string): void { - typeText(this.getSearchInput(), searchTerm); + this.getSearchInput().type(searchTerm); } public countSearchEntries(): Cypress.Chainable<number> { diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/app/user-profile-menu.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/app/user-profile-menu.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..19852a52775d1f104314441a3a85ce466a7df7f0 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/app/user-profile-menu.cy.ts @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import { getUrl } from '@ngxp/rest'; +import { HttpMethodE2E } from '../../../model/util'; +import { HeaderE2EComponent } from '../../../page-objects/header.po'; +import { MainPage } from '../../../page-objects/main.po'; +import { getBaseUrl, intercept, waitOfInterceptor } from '../../../support/cypress-helper'; +import { exist, shouldHaveAttribute } from '../../../support/cypress.util'; +import { ApiRootLinkRelE2E } from '../../../support/linkrels'; +import { loginAsAriane } from '../../../support/user-util'; + +describe('User Profile Menu', () => { + const mainPage: MainPage = new MainPage(); + const header: HeaderE2EComponent = mainPage.getHeader(); + + let documentationLink: string = ''; + + before(() => { + const interceptor: string = 'getApiRoot'; + intercept(HttpMethodE2E.GET, `${getBaseUrl()}/api`).as(interceptor); + loginAsAriane(); + waitOfInterceptor(interceptor).then( + (interception) => (documentationLink = getUrl(interception?.response?.body, ApiRootLinkRelE2E.DOCUMENTATIONS)), + ); + }); + + describe('open user profile menu', () => { + before(() => { + header.getCurrentUserProfile().getUserIconButton().click(); + }); + + it('should show logout button', () => { + exist(header.getCurrentUserProfile().getLogoutButton()); + }); + + it('should show documentation', () => { + exist(header.getCurrentUserProfile().getDocumentation()); + }); + + it('should find documentation link', () => { + shouldHaveAttribute(header.getCurrentUserProfile().getDocumentation().find('a'), 'href', documentationLink); + }); + }); +}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-anlegen.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-anlegen.cy.ts index 0b9aad6ab869bad69f47e7c59cbf0b85e39abe61..e2cbf35c6a1488905f98fa26df189a6fe1425ccd 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-anlegen.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-anlegen.cy.ts @@ -1,69 +1,65 @@ - -import { MainPage } from 'apps/admin-e2e/src/page-objects/main.po'; -import { BenutzerE2EComponent } from '../../../components/benutzer/benutzer.e2e.component'; -import { beChecked, beEnabled, contains, exist, notBeChecked, notBeEnabled } from '../../../support/cypress.util'; -import { AlfaRollen, AlfaUsers, loginAsAriane } from '../../../support/user-util'; +import { E2EBenutzerHelper } from 'apps/admin-e2e/src/helper/benutzer/benutzer.helper'; +import { E2EBenutzerVerifier } from 'apps/admin-e2e/src/helper/benutzer/benutzer.verifier'; +import { getCypressEnv, interceptWithResponse, waitOfInterceptor } from 'apps/admin-e2e/src/support/cypress-helper'; import { SnackBarE2EComponent } from '../../../components/ui/snackbar.e2e.component'; -import { SnackbarMessagesE2E } from '../../../model/util'; -import { getCypressEnv, interceptWithResponse, wait, waitOfInterceptor } from 'apps/admin-e2e/src/support/cypress-helper'; -import { AdminUserE2E, HttpMethodE2E } from '../../../model/util'; +import { AdminUserE2E, HttpMethodE2E, SnackbarMessagesE2E } from '../../../model/util'; +import { contains, notExist } from '../../../support/cypress.util'; +import { loginAsAriane } from '../../../support/user-util'; -const mainPage: MainPage = new MainPage(); -const benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); -const snackBar: SnackBarE2EComponent = new SnackBarE2EComponent(); +describe('Benutzer anlegen', () => { + const benutzerHelper: E2EBenutzerHelper = new E2EBenutzerHelper(); + const benutzerVerifier: E2EBenutzerVerifier = new E2EBenutzerVerifier(); -const vorname: string = 'Theo'; -const nachname: string = 'Testuser'; -const benutzername: string = 'testtheo'; -const emailAddress: string = 'theo.test@ozg-sh.de'; + const snackBar: SnackBarE2EComponent = new SnackBarE2EComponent(); -const newUser: AdminUserE2E = { - vorname: 'Theo', - nachname: 'Testuser', - benutzername: 'testtheo', - email: 'theo.test@ozg-sh.de', - isAdmin: true, - isUser: true, -} + const newUser: AdminUserE2E = { + vorname: 'Theo', + nachname: 'Testuser', + username: 'testtheo', + email: 'theo.test@ozg-sh.de', + isAdmin: true, + isUser: true, + organisationseinheiten: [], + }; -describe('Benutzer anlegen', () => { before(() => { loginAsAriane(); }); - it('should open Benutzer tab and show Hinzufuegen button', () => { - mainPage.clickBenutzerNavigationItem(); - - exist(benutzerPage.getHinzufuegenButton()); - }); - - it('should show snackbar message on error', () => { + it('should show error snackbar after save and receiving error response', () => { const interceptor: string = 'postUser'; - const url: string = getCypressEnv('keycloakUrl') + '/admin/realms/' + getCypressEnv('keycloakRealm') + '/users' + const url: string = getCypressEnv('keycloakUrl') + '/admin/realms/' + getCypressEnv('keycloakRealm') + '/users'; const errorCode: number = 500; const errorBody: string = 'Internal Server Error'; - interceptWithResponse(HttpMethodE2E.POST, url, { errorCode, errorBody }).as(interceptor); + benutzerHelper.openNewBenutzerPage(); - benutzerPage.clickAddUser(); - benutzerPage.addUser(newUser); + benutzerHelper.addBenutzer(newUser); + benutzerHelper.saveBenutzer(); waitOfInterceptor(interceptor).then(() => { contains(snackBar.getMessage(), SnackbarMessagesE2E.NUTZER_FEHLGESCHLAGEN); + snackBar.getCloseButton().click(); + notExist(snackBar.getMessage()); }); }); - it('should show snackbar message on saving user', () => { - snackBar.getCloseButton().click(); + it('should show snackbar after save', () => { + benutzerHelper.openNewBenutzerPage(); + + benutzerHelper.addBenutzer(newUser); + benutzerHelper.saveBenutzer(); - benutzerPage.saveUser(); contains(snackBar.getMessage(), SnackbarMessagesE2E.NUTZER_ANGELEGT); + snackBar.getCloseButton().click(); + }); + + it('should show created user in list', () => { + benutzerVerifier.verifyUserInList(newUser); }); - it('should display new user in users table', () => { - benutzerPage.stringExistsInUserEntry(AlfaRollen.USER, benutzername); - benutzerPage.stringExistsInUserEntry(vorname, benutzername); - benutzerPage.stringExistsInUserEntry(nachname, benutzername); - // FEHLT NOCH: benutzerPage.stringExistsInUserEntry(AlfaRollen.ADMIN, benutzername); + it('should remove benutzer', () => { + benutzerHelper.deleteBenutzer(newUser.username); + benutzerVerifier.verifyUserNotInList(newUser.username); }); }); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-loesche.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-loesche.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd494a41834116ef96c2ed992e026fba95bb53ef --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-loesche.cy.ts @@ -0,0 +1,33 @@ +import { faker } from '@faker-js/faker'; +import { E2EBenutzerHelper } from 'apps/admin-e2e/src/helper/benutzer/benutzer.helper'; +import { E2EBenutzerVerifier } from 'apps/admin-e2e/src/helper/benutzer/benutzer.verifier'; +import { AdminUserE2E } from 'apps/admin-e2e/src/model/util'; +import { loginAsAriane } from 'apps/admin-e2e/src/support/user-util'; + +describe('Benutzer Löschen', () => { + const benutzerVerifier: E2EBenutzerVerifier = new E2EBenutzerVerifier(); + const benutzerHelper: E2EBenutzerHelper = new E2EBenutzerHelper(); + + const userName: string = 'testtheo' + faker.string.uuid(); + const user: AdminUserE2E = { + vorname: 'Theo', + nachname: 'Testuser', + username: userName, + email: 'theo' + faker.string.uuid() + '@ozg-sh.de', + isUser: true, + organisationseinheiten: [], + }; + + before(() => { + loginAsAriane(); + }); + + it('should delete user', () => { + benutzerHelper.openNewBenutzerPage(); + benutzerHelper.addBenutzerAndSave(user); + + benutzerHelper.deleteBenutzer(userName); + + benutzerVerifier.verifyUserNotInList(userName); + }); +}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-zu-oe-hinzufuegen.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-zu-oe-hinzufuegen.cy.ts index 9095c63279a17ffe2cbada1f4de9f956593c0638..2a6fc233600a14024c3d62d7056afb41effa8750 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-zu-oe-hinzufuegen.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-zu-oe-hinzufuegen.cy.ts @@ -1,110 +1,84 @@ -import { OrganisationsEinheitenE2EComponent } from 'apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component'; -import { ZustaendigeStelleDialogE2EComponent } from 'apps/admin-e2e/src/components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component'; +import { E2EBenutzerHelper } from 'apps/admin-e2e/src/helper/benutzer/benutzer.helper'; +import { E2EBenutzerVerifier } from 'apps/admin-e2e/src/helper/benutzer/benutzer.verifier'; +import { OrganisationsEinheitE2E } from 'apps/admin-e2e/src/model/organisations-einheit'; import { AdminUserE2E } from 'apps/admin-e2e/src/model/util'; -import { MainPage } from 'apps/admin-e2e/src/page-objects/main.po'; -import { BenutzerE2EComponent } from '../../../components/benutzer/benutzer.e2e.component'; -import { beChecked, exist, notBeChecked } from '../../../support/cypress.util'; import { loginAsAriane } from '../../../support/user-util'; describe('Organisationseinheit zu Benutzer hinzufügen', () => { - const mainPage: MainPage = new MainPage(); - const benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); - const organisationsEinheitenComponent: OrganisationsEinheitenE2EComponent = new OrganisationsEinheitenE2EComponent(); - const zustaendigeStelleSearchComponent: ZustaendigeStelleDialogE2EComponent = new ZustaendigeStelleDialogE2EComponent(); - const organistationsEinheitOrdnungsamt: string = 'Ordnungsamt'; - const organisationsEinheitDenkmalpflege: string = 'Denkmalpflege'; - const organisationsEinheitLiegenschaften: string = 'Liegenschaften'; - const organisationsEinheitNone: string = 'keine zuständige Stelle zugewiesen'; + const benutzerVerifier: E2EBenutzerVerifier = new E2EBenutzerVerifier(); + const benutzerHelper: E2EBenutzerHelper = new E2EBenutzerHelper(); + const vorname: string = 'Theo'; const nachname: string = 'Testuser'; + const now1 = new Date(); - const benutzername1: string = 'testtheo' + now1.getSeconds().toString() + now1.getMilliseconds().toString(); + const userName1: string = 'testtheo' + now1.getSeconds().toString() + now1.getMilliseconds().toString(); const emailAddress1: string = 'theo' + now1.getSeconds().toString() + now1.getMilliseconds().toString() + '@ozg-sh.de'; + const now2 = new Date(now1.getTime() + 1000); - const benutzername2: string = 'testtheo' + now2.getSeconds().toString() + now2.getMilliseconds().toString(); + const userName2: string = 'testtheo' + now2.getSeconds().toString() + now2.getMilliseconds().toString(); const emailAddress2: string = 'theo' + now2.getSeconds().toString() + now2.getMilliseconds().toString() + '@ozg-sh.de'; - const newUser1: AdminUserE2E = { + const userWithoutOrganisationsEinheiten: AdminUserE2E = { vorname: vorname, nachname: nachname, - benutzername: benutzername1, + username: userName1, email: emailAddress1, isUser: true, + organisationseinheiten: [], }; - const newUser2: AdminUserE2E = { + const userWithOrganisationsEinheiten: AdminUserE2E = { vorname: vorname, nachname: nachname, - benutzername: benutzername2, + username: userName2, email: emailAddress2, isUser: true, - organisationseinheiten: ['Denkmalpflege'], + organisationseinheiten: [OrganisationsEinheitE2E.DENKMALPFLEGE], }; before(() => { loginAsAriane(); }); - it('should click Hinzufügen button and show Organisationseinheiten in Benutzer page', () => { - mainPage.benutzerNavigationItemIsVisible(); - mainPage.clickBenutzerNavigationItem(); - benutzerPage.hinzufuegenButtonIsVisible(); + it('should show organisationseinheiten in formular', () => { + benutzerHelper.openNewBenutzerPage(); - benutzerPage.clickAddUser(); - exist(benutzerPage.getOrganisationsEinheitCheckbox(organisationsEinheitDenkmalpflege)); - exist(benutzerPage.getOrganisationsEinheitCheckbox(organistationsEinheitOrdnungsamt)); + benutzerVerifier.verifyOrganisationsEinheitenInFormular([ + OrganisationsEinheitE2E.DENKMALPFLEGE, + OrganisationsEinheitE2E.ORDNUNGSAMT, + ]); }); - it('should add no Organisationseinheit to new user without selection and saving', () => { - benutzerPage.addUser(newUser1); + it('should add new user without organisationseinheiten', () => { + benutzerHelper.openNewBenutzerPage(); - benutzerPage.stringExistsInUserEntry(organisationsEinheitNone, benutzername1); - }); + benutzerHelper.addBenutzerAndSave(userWithoutOrganisationsEinheiten); - it('should add Organisationseinheit to new user after selection and saving', () => { - benutzerPage.clickAddUser(); - benutzerPage.addUser(newUser2); - - benutzerPage.stringExistsInUserEntry(organisationsEinheitDenkmalpflege, benutzername2); + benutzerVerifier.verifyNoOrganisationsEinheitInBenutzerList(userName1); }); - it('should remove Organisationseinheit from existing user on deselection and save', () => { - benutzerPage.clickUserEntry(benutzername2); - beChecked(benutzerPage.getOrganisationsEinheitCheckbox(organisationsEinheitDenkmalpflege)); + it('should add new user with organisationseinheiten', () => { + benutzerHelper.openNewBenutzerPage(); - benutzerPage.clickOrganisationsEinheitCheckbox(organisationsEinheitDenkmalpflege); - benutzerPage.saveUser(); + benutzerHelper.addBenutzerAndSave(userWithOrganisationsEinheiten); - benutzerPage.stringExistsInUserEntry(organisationsEinheitNone, benutzername2); + benutzerVerifier.verfiyOrganisationsEinheitInBenutzerList(OrganisationsEinheitE2E.DENKMALPFLEGE, userName2); }); - it('should add Organisationseinheit to existing user on selection and save', () => { - benutzerPage.clickUserEntry(benutzername2); - notBeChecked(benutzerPage.getOrganisationsEinheitCheckbox(organistationsEinheitOrdnungsamt)); + it('should remove organisationseinheit from existing user', () => { + benutzerHelper.openBenutzerPage(userName2); - benutzerPage.clickOrganisationsEinheitCheckbox(organistationsEinheitOrdnungsamt); - benutzerPage.saveUser(); + benutzerHelper.editOrganisationsEinheitenAndSave([OrganisationsEinheitE2E.DENKMALPFLEGE]); - benutzerPage.stringExistsInUserEntry(organistationsEinheitOrdnungsamt, benutzername2); + benutzerVerifier.verifyNoOrganisationsEinheitInBenutzerList(userName2); }); - it('should enable new Organisationseinheit for users after adding it', () => { - mainPage.clickOrganisationsEinheitenNavigationItem(); - organisationsEinheitenComponent.clickHinzufuegen(); - zustaendigeStelleSearchComponent.enterSearchTerm(organisationsEinheitLiegenschaften); - zustaendigeStelleSearchComponent.getZustaendigeStelleTitle(0).then((title: string) => { - zustaendigeStelleSearchComponent.clickFoundItem(0); - }); - - mainPage.clickBenutzerNavigationItem(); - benutzerPage.clickUserEntry(benutzername2); + it('should add organisationseinheit to existing user', () => { + benutzerHelper.openBenutzerPage(userName2); - exist(benutzerPage.getOrganisationsEinheitCheckbox(organisationsEinheitLiegenschaften)); - }); + benutzerHelper.editOrganisationsEinheitenAndSave([OrganisationsEinheitE2E.ORDNUNGSAMT]); - it('should add new Organisationseinheit to existing user', () => { - benutzerPage.clickOrganisationsEinheitCheckbox(organisationsEinheitLiegenschaften); - benutzerPage.saveUser(); - benutzerPage.stringExistsInUserEntry(organisationsEinheitLiegenschaften, benutzername2); + benutzerVerifier.verfiyOrganisationsEinheitInBenutzerList(OrganisationsEinheitE2E.ORDNUNGSAMT, userName2); }); }); 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 9514df893c043a400aa61ec1e517a6007846eb68..205d661f91365f4bea89867b41ae673a7f1e2c4a 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 @@ -21,101 +21,144 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { MainPage } from 'apps/admin-e2e/src/page-objects/main.po'; -import { BenutzerE2EComponent } from '../../../components/benutzer/benutzer.e2e.component'; -import { beChecked, beEnabled, exist, notBeChecked, notBeEnabled } from '../../../support/cypress.util'; -import { AlfaRollen, AlfaUsers, loginAsAriane } from '../../../support/user-util'; - -const mainPage: MainPage = new MainPage(); -const benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); -const organisationsEinheitOrdnungsamt: string = 'Ordnungsamt'; -const organisationsEinheitDenkmalpflege: string = 'Denkmalpflege'; -const organisationsEinheitWirtschaftsfoerderung: string = 'Wirtschaftsförderung'; -const organisationsEinheitNone: string = 'keine zuständige Stelle zugewiesen'; -const emailAddress: string = 'peter.von.der.post@ozg-sh.de'; +import { BenutzerE2EComponent, BenutzerListE2EComponent, BenutzerListItemE2EComponent, } from 'apps/admin-e2e/src/components/benutzer/benutzer.e2e.component'; +import { E2EBenutzerHelper } from 'apps/admin-e2e/src/helper/benutzer/benutzer.helper'; +import { OrganisationsEinheitE2E } from 'apps/admin-e2e/src/model/organisations-einheit'; +import { beChecked, beEnabled, contains, exist, mouseEnter, notBeChecked, notBeEnabled, visible, } from 'apps/admin-e2e/src/support/cypress.util'; +import { AlfaRollen, AlfaUsers, loginAsAriane } from 'apps/admin-e2e/src/support/user-util'; describe('Benutzer und Rollen', () => { + const benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); + const benutzerListPage: BenutzerListE2EComponent = new BenutzerListE2EComponent(); + + //TODO In der entsprechenden json hinterlegen und darauf zugreifen + const emailAddress: string = 'peter.von.der.post@ozg-sh.de'; + + const helper: E2EBenutzerHelper = new E2EBenutzerHelper(); + before(() => { loginAsAriane(); }); - it('should open Benutzer tab and show Hinzufuegen button', () => { - mainPage.clickBenutzerNavigationItem(); + it('should show users and attributes in list', () => { + helper.openBenutzerListPage(); - exist(benutzerPage.getHinzufuegenButton()); - }); + const ariane: BenutzerListItemE2EComponent = benutzerListPage.getItem(AlfaUsers.ARIANE); + exist(ariane.getRoot()); + contains(ariane.getRoles(), AlfaRollen.USER); + + const dorothea: BenutzerListItemE2EComponent = benutzerListPage.getItem(AlfaUsers.DOROTHEA); + exist(dorothea.getRoot()); + contains(dorothea.getRoles(), AlfaRollen.USER); - it('should show users and attributes in table', () => { - exist(benutzerPage.getUserEntry('ariane')); - benutzerPage.stringExistsInUserEntry(AlfaRollen.USER, AlfaUsers.DOROTHEA); - benutzerPage.stringExistsInUserEntry(organisationsEinheitOrdnungsamt, AlfaUsers.LUDWIG); - benutzerPage.stringExistsInUserEntry(AlfaRollen.USER, AlfaUsers.ZELDA); - benutzerPage.stringExistsInUserEntry(AlfaRollen.LOESCHEN, AlfaUsers.LUDWIG); - benutzerPage.stringExistsInUserEntry(organisationsEinheitDenkmalpflege, AlfaUsers.ZELDA); - benutzerPage.stringExistsInUserEntry(organisationsEinheitWirtschaftsfoerderung, AlfaUsers.ZELDA); - benutzerPage.stringExistsInUserEntry(organisationsEinheitNone, AlfaUsers.RICHARD); - benutzerPage.stringExistsInUserEntry(emailAddress, AlfaUsers.PETER); - benutzerPage.stringExistsInUserEntry(AlfaRollen.POSTSTELLE, AlfaUsers.PETER); + const ludwig: BenutzerListItemE2EComponent = benutzerListPage.getItem(AlfaUsers.LUDWIG); + exist(ludwig.getRoot()); + contains(ludwig.getRoles(), AlfaRollen.LOESCHEN); + contains(ludwig.getOrganisationsEinheiten(), OrganisationsEinheitE2E.ORDNUNGSAMT); + + const zelda: BenutzerListItemE2EComponent = benutzerListPage.getItem(AlfaUsers.ZELDA); + exist(zelda.getRoot()); + contains(zelda.getRoles(), AlfaRollen.USER); + contains(zelda.getOrganisationsEinheiten(), OrganisationsEinheitE2E.DENKMALPFLEGE); + contains(zelda.getOrganisationsEinheiten(), OrganisationsEinheitE2E.WIRTSCHAFTSFOERDERUNG); + + const peter: BenutzerListItemE2EComponent = benutzerListPage.getItem(AlfaUsers.PETER); + exist(peter.getRoot()); + contains(peter.getRoles(), AlfaRollen.POSTSTELLE); + contains(peter.getEMail(), emailAddress); + + const richard: BenutzerListItemE2EComponent = benutzerListPage.getItem(AlfaUsers.RICHARD); + exist(richard.getRoot()); + exist(richard.getNoOrganisationsEinheitText()); }); - it('should show single user screen on click', () => { - benutzerPage.clickAddUser(); + it('should show checkbox for each role', () => { + helper.openNewBenutzerPage(); + + notBeChecked(benutzerPage.getAdminCheckbox().getRoot()); + notBeChecked(benutzerPage.getDatenbeauftragungCheckbox().getRoot()); + notBeChecked(benutzerPage.getLoeschenCheckbox().getRoot()); + notBeChecked(benutzerPage.getUserCheckbox().getRoot()); + notBeChecked(benutzerPage.getPostCheckbox().getRoot()); + }); - exist(benutzerPage.getVornameInput()); - exist(benutzerPage.getNachnameInput()); - exist(benutzerPage.getBenutzernameInput()); - exist(benutzerPage.getMailInput()); + it('should deactivate other alfa roles if "loeschen" role is selected', () => { + benutzerPage.getLoeschenCheckbox().getRoot().click(); + beChecked(benutzerPage.getLoeschenCheckbox().getRoot()); + notBeEnabled(benutzerPage.getUserCheckbox().getRoot()); + notBeEnabled(benutzerPage.getPostCheckbox().getRoot()); - notBeChecked(benutzerPage.getAdminCheckbox()); - notBeChecked(benutzerPage.getLoeschenCheckbox()); - notBeChecked(benutzerPage.getUserCheckbox()); - notBeChecked(benutzerPage.getPostCheckbox()); + benutzerPage.getLoeschenCheckbox().getRoot().click(); + notBeChecked(benutzerPage.getLoeschenCheckbox().getRoot()); + beEnabled(benutzerPage.getUserCheckbox().getRoot()); + beEnabled(benutzerPage.getPostCheckbox().getRoot()); }); - it('should activate loeschen checkbox and deactivate the other two checkboxes', () => { - benutzerPage.clickLoeschenCheckbox(); - beChecked(benutzerPage.getLoeschenCheckbox()); - notBeEnabled(benutzerPage.getUserCheckbox()); - notBeEnabled(benutzerPage.getPostCheckbox()); + it('should deactivate other alfa roles if "user" role is selected', () => { + benutzerPage.getUserCheckbox().getRoot().click(); + beChecked(benutzerPage.getUserCheckbox().getRoot()); + notBeEnabled(benutzerPage.getLoeschenCheckbox().getRoot()); + notBeEnabled(benutzerPage.getPostCheckbox().getRoot()); - benutzerPage.clickLoeschenCheckbox(); - notBeChecked(benutzerPage.getLoeschenCheckbox()); - beEnabled(benutzerPage.getUserCheckbox()); - beEnabled(benutzerPage.getPostCheckbox()); + benutzerPage.getUserCheckbox().getRoot().click(); + notBeChecked(benutzerPage.getUserCheckbox().getRoot()); + beEnabled(benutzerPage.getLoeschenCheckbox().getRoot()); + beEnabled(benutzerPage.getPostCheckbox().getRoot()); }); - it('should additionally activate and deactivate admin checkbox', () => { - benutzerPage.clickLoeschenCheckbox(); - benutzerPage.clickAdminCheckbox(); - beChecked(benutzerPage.getLoeschenCheckbox()); - beChecked(benutzerPage.getAdminCheckbox()); + it('should deactivate other alfa roles if "poststelle" role is selected', () => { + benutzerPage.getPostCheckbox().getRoot().click(); + beChecked(benutzerPage.getPostCheckbox().getRoot()); + notBeEnabled(benutzerPage.getLoeschenCheckbox().getRoot()); + notBeEnabled(benutzerPage.getUserCheckbox().getRoot()); - benutzerPage.clickAdminCheckbox(); - notBeChecked(benutzerPage.getAdminCheckbox()); + benutzerPage.getPostCheckbox().getRoot().click(); + notBeChecked(benutzerPage.getPostCheckbox().getRoot()); + beEnabled(benutzerPage.getLoeschenCheckbox().getRoot()); + beEnabled(benutzerPage.getUserCheckbox().getRoot()); }); - 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 and deactivate admin roles', () => { + benutzerPage.getAdminCheckbox().getRoot().click(); + benutzerPage.getDatenbeauftragungCheckbox().getRoot().click(); + beChecked(benutzerPage.getAdminCheckbox().getRoot()); + beChecked(benutzerPage.getDatenbeauftragungCheckbox().getRoot()); + + benutzerPage.getAdminCheckbox().getRoot().click(); + benutzerPage.getDatenbeauftragungCheckbox().getRoot().click(); + notBeChecked(benutzerPage.getAdminCheckbox().getRoot()); + notBeChecked(benutzerPage.getDatenbeauftragungCheckbox().getRoot()); }); - it('should activate post checkbox and deactivate the other two checkboxes', () => { - benutzerPage.clickPostCheckbox(); - beChecked(benutzerPage.getPostCheckbox()); - notBeEnabled(benutzerPage.getLoeschenCheckbox()); - notBeEnabled(benutzerPage.getUserCheckbox()); + describe('hint text', () => { + it('should be visible on admin role mouse hover', () => { + mouseEnter(benutzerPage.getAdminCheckbox().getInfoButton()); + + visible(benutzerPage.getAdminCheckbox().getInfoButtonTooltip()); + }); + + it('should be visible on loeschen role mouse hover', () => { + mouseEnter(benutzerPage.getLoeschenCheckbox().getInfoButton()); + + visible(benutzerPage.getLoeschenCheckbox().getInfoButtonTooltip()); + }); + + it('should be visible on user role mouse hover', () => { + mouseEnter(benutzerPage.getUserCheckbox().getInfoButton()); + + visible(benutzerPage.getUserCheckbox().getInfoButtonTooltip()); + }); + + it('should be visible on poststellt role mouse hover', () => { + mouseEnter(benutzerPage.getPostCheckbox().getInfoButton()); + + visible(benutzerPage.getPostCheckbox().getInfoButtonTooltip()); + }); + + it('should be visible on datenbeauftragung role mouse hover', () => { + mouseEnter(benutzerPage.getDatenbeauftragungCheckbox().getInfoButton()); - benutzerPage.clickPostCheckbox(); - notBeChecked(benutzerPage.getPostCheckbox()); - beEnabled(benutzerPage.getLoeschenCheckbox()); - beEnabled(benutzerPage.getUserCheckbox()); + visible(benutzerPage.getDatenbeauftragungCheckbox().getInfoButtonTooltip()); + }); }); }); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/ariane.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/ariane.cy.ts index 80987765996348c4f24f3105abc2328713ef4371..64218bf8c50d791ca4f221c518a454cc54df37fe 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/ariane.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/ariane.cy.ts @@ -1,29 +1,33 @@ -import { MainPage, waitForSpinnerToDisappear } from 'apps/admin-e2e/src/page-objects/main.po'; -import { exist, notExist } from 'apps/admin-e2e/src/support/cypress.util'; +import { MainPage } from 'apps/admin-e2e/src/page-objects/main.po'; +import { containClass, exist, notExist } from 'apps/admin-e2e/src/support/cypress.util'; import { loginAsAriane } from 'apps/admin-e2e/src/support/user-util'; -describe('Navigation', () => { +describe('Ariane Navigation', () => { const mainPage: MainPage = new MainPage(); describe('with user ariane', () => { before(() => { loginAsAriane(); - - waitForSpinnerToDisappear(); }); it('should show benutzer navigation item', () => { exist(mainPage.getBenutzerNavigationItem()); }); - it('should show postfach navigation item', () => { - exist(mainPage.getPostfachNavigationItem()); - }); - it('should show organisationseinheiten navigation item', () => { exist(mainPage.getOrganisationEinheitNavigationItem()); }); + describe('postfach navigation item', () => { + it('should be visible', () => { + exist(mainPage.getPostfachNavigationItem()); + }); + + it('should be selected initial', () => { + containClass(mainPage.getPostfachNavigationItem(), 'border-selected'); + }); + }); + it('should hide statistik navigation item', () => { notExist(mainPage.getStatistikNavigationItem()); }); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/daria.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/daria.cy.ts index 57867568976e5deb0675a0ccd3fc52f9e5e19d79..4fda0a2dee6bfb74ad354a08e46f27ced5e15bc6 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/daria.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/daria.cy.ts @@ -1,43 +1,34 @@ -import { MainPage, waitForSpinnerToDisappear } from 'apps/admin-e2e/src/page-objects/main.po'; -import { exist, notExist } from 'apps/admin-e2e/src/support/cypress.util'; +import { MainPage } from 'apps/admin-e2e/src/page-objects/main.po'; +import { containClass, exist, notExist } from 'apps/admin-e2e/src/support/cypress.util'; import { loginAsDaria } from 'apps/admin-e2e/src/support/user-util'; -import { StatistikE2EComponent } from '../../../components/statistik/statistik.e2e.component'; -describe('Navigation', () => { +describe('Daria Navigation', () => { const mainPage: MainPage = new MainPage(); - const statistikPage: StatistikE2EComponent = new StatistikE2EComponent(); - describe('with user daria', () => { before(() => { loginAsDaria(); - - waitForSpinnerToDisappear(); }); - it('should hide other navigation item', () => { + it('should hide benutzer navigation item', () => { notExist(mainPage.getBenutzerNavigationItem()); }); - it('should hide postfach navigation item', () => { - notExist(mainPage.getPostfachNavigationItem()); - }); - it('should hide organisationseinheiten navigation item', () => { notExist(mainPage.getOrganisationEinheitNavigationItem()); }); - describe('statistik', () => { + it('should hide postfach navigation item', () => { + notExist(mainPage.getPostfachNavigationItem()); + }); + + describe('statistik navigation item', () => { it('should be visible', () => { exist(mainPage.getStatistikNavigationItem()); }); it('should be initial selected', () => { - mainPage.isStatistikNavigationItemSelected(); - }); - - it('should show header text', () => { - statistikPage.isHeaderTextVisible(); + containClass(mainPage.getStatistikNavigationItem(), 'border-selected'); }); }); }); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/safira.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/safira.cy.ts index 6f76dafdfc3161646383b34465056c2e751fa33c..ae328f579e493b6c493a687da703269120bebbd5 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/safira.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/navigation/safira.cy.ts @@ -1,50 +1,35 @@ -import { MainPage, waitForSpinnerToDisappear } from 'apps/admin-e2e/src/page-objects/main.po'; -import { exist } from 'apps/admin-e2e/src/support/cypress.util'; +import { MainPage } from 'apps/admin-e2e/src/page-objects/main.po'; +import { containClass, exist } from 'apps/admin-e2e/src/support/cypress.util'; import { loginAsSafira } from 'apps/admin-e2e/src/support/user-util'; -import { StatistikE2EComponent } from '../../../components/statistik/statistik.e2e.component'; -describe('Navigation', () => { +describe('Safira Navigation', () => { const mainPage: MainPage = new MainPage(); - const statistikPage: StatistikE2EComponent = new StatistikE2EComponent(); - describe('with user safira', () => { before(() => { loginAsSafira(); - - waitForSpinnerToDisappear(); }); it('should show benutzer navigation item', () => { exist(mainPage.getBenutzerNavigationItem()); }); - it('should show postfach navigation item', () => { - exist(mainPage.getPostfachNavigationItem()); - }); - it('should show organisationseinheiten navigation item', () => { exist(mainPage.getOrganisationEinheitNavigationItem()); }); - describe('statistik', () => { + describe('postfach navigation item', () => { it('should be visible', () => { - exist(mainPage.getStatistikNavigationItem()); + exist(mainPage.getPostfachNavigationItem()); }); - describe('on selection', () => { - before(() => { - mainPage.clickStatistikNavigationItem(); - }); - - it('should show page on selection', () => { - statistikPage.isHeaderTextVisible(); - }); - - it('should mark navigation item as selected', () => { - mainPage.isStatistikNavigationItemSelected(); - }); + it('should be selected initial', () => { + containClass(mainPage.getPostfachNavigationItem(), 'border-selected'); }); }); + + it('should show statistik navigation item', () => { + exist(mainPage.getStatistikNavigationItem()); + }); }); }); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheit.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheit.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f52b488067ab890200384cfe2d0eba448c87078 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheit.cy.ts @@ -0,0 +1,53 @@ +import { E2EOrganisationsEinheitHelper } from 'apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper'; +import { E2EOrganisationsEinheitVerifier } from 'apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.verifier'; +import { OrganisationsEinheitPage } from 'apps/admin-e2e/src/page-objects/organisations-einheit.po'; +import { ZustaendigeStelleDialogE2EComponent } from '../../../components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component'; +import { exist } from '../../../support/cypress.util'; +import { loginAsAriane } from '../../../support/user-util'; + +describe('Organisationseinheit', () => { + const organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + + const organisationsEinheitHelper: E2EOrganisationsEinheitHelper = new E2EOrganisationsEinheitHelper(); + const organisationsEinheitVerifier: E2EOrganisationsEinheitVerifier = new E2EOrganisationsEinheitVerifier(); + + const zustaendigeStelleSearchComponent: ZustaendigeStelleDialogE2EComponent = new ZustaendigeStelleDialogE2EComponent(); + + const organisationsEinheit: string = 'Wasserwerk - Hamburg Wasser - Hamburger Stadtentwässerung'; + + before(() => { + loginAsAriane(); + }); + + describe('hinzufügen', () => { + it('should show search dialog on add button click', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + organisationsEinheitPage.getAddButton().click(); + + exist(zustaendigeStelleSearchComponent.getZustaendigeStelleForm()); + }); + + it('should find at least one organisationseinheit on search', () => { + zustaendigeStelleSearchComponent.enterSearchTerm(organisationsEinheit); + + zustaendigeStelleSearchComponent.expectNumberOfEntriesToBeGreaterThan(1); + }); + + it('should show organisationseinheit in list', () => { + organisationsEinheitHelper.addOrganisationsEinheit(organisationsEinheit); + + organisationsEinheitVerifier.verifyOrganisationsEinheitInList(organisationsEinheit); + }); + }); + + describe('löschen', () => { + it('should not show entry in list', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + organisationsEinheitHelper.deleteOrganisationsEinheit(organisationsEinheit); + + organisationsEinheitVerifier.verifyOrganisationsEinheitNotInList(organisationsEinheit); + }); + }); +}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheiten-page.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheiten-page.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..c17724c86bb9ed02eacc5f5f88bbcf35e865b79a --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisations-einheiten-page.cy.ts @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import { OrganisationsEinheitListE2EComponent } from 'apps/admin-e2e/src/components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { E2EOrganisationsEinheitHelper } from 'apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper'; +import { OrganisationsEinheitE2E } from 'apps/admin-e2e/src/model/organisations-einheit'; +import { OrganisationsEinheitPage } from 'apps/admin-e2e/src/page-objects/organisations-einheit.po'; +import { exist } from '../../../support/cypress.util'; +import { loginAsAriane } from '../../../support/user-util'; + +describe('Organisationsheiten page', () => { + const organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + + const organisationsEinheitHelper: E2EOrganisationsEinheitHelper = new E2EOrganisationsEinheitHelper(); + + const organisationsEinheitList: OrganisationsEinheitListE2EComponent = new OrganisationsEinheitListE2EComponent(); + + before(() => { + loginAsAriane(); + }); + + it('should show list', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + exist(organisationsEinheitPage.getList().getRoot()); + }); + + it('should show add button', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + exist(organisationsEinheitPage.getAddButton()); + }); + + it('should show default (Bauamt, Fundstelle, Denkmalpflege) entries', () => { + organisationsEinheitHelper.openOrganisationsEinheitPage(); + + exist(organisationsEinheitList.getListItem(OrganisationsEinheitE2E.BAUAMT).getRoot()); + exist(organisationsEinheitList.getListItem(OrganisationsEinheitE2E.FUNDSTELLE).getRoot()); + exist(organisationsEinheitList.getListItem(OrganisationsEinheitE2E.DENKMALPFLEGE).getRoot()); + }); +}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-hinzufuegen.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-hinzufuegen.cy.ts deleted file mode 100644 index ecdc072a69ee0a81f41aa786ccb0a23eacc4f450..0000000000000000000000000000000000000000 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-hinzufuegen.cy.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { OrganisationsEinheitenE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten.e2e.component'; -import { ZustaendigeStelleDialogE2EComponent } from '../../../components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component'; -import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; -import { exist } from '../../../support/cypress.util'; -import { loginAsAriane } from '../../../support/user-util'; - -describe('Organisationseinheiten', () => { - const mainPage: MainPage = new MainPage(); - const organisationsEinheitenComponent: OrganisationsEinheitenE2EComponent = new OrganisationsEinheitenE2EComponent(); - const zustaendigeStelleSearchComponent: ZustaendigeStelleDialogE2EComponent = new ZustaendigeStelleDialogE2EComponent(); - - const searchTerm: string = 'Hamburg'; - - before(() => { - loginAsAriane(); - }); - - it('should show table with Organisationseinheiten', () => { - waitForSpinnerToDisappear(); - mainPage.clickOrganisationsEinheitenNavigationItem(); - - exist(organisationsEinheitenComponent.getOrganisationsEinheitHinzufuegenButton()); - }); - - it('should show button to add Organisationseinheit', () => { - exist(organisationsEinheitenComponent.getOrganisationsEinheitHinzufuegenButton()); - }); - - it('should show search Organisationseinheit dialog', () => { - organisationsEinheitenComponent.clickHinzufuegen(); - - exist(zustaendigeStelleSearchComponent.getZustaendigeStelleForm()); - }); - - it('should find at least one Organisationseinheit', () => { - zustaendigeStelleSearchComponent.enterSearchTerm(searchTerm); - - zustaendigeStelleSearchComponent.expectNumberOfEntriesToBeGreaterThan(1); - }); - - it('should add first Organisationseinheit', () => { - zustaendigeStelleSearchComponent.getZustaendigeStelleTitle(0).then((title: string) => { - zustaendigeStelleSearchComponent.clickFoundItem(0); - exist(organisationsEinheitenComponent.getListItemByName(title)); - }); - }); -}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts deleted file mode 100644 index 91a1673e96e41b8dbdae4e4338e864a0527f816e..0000000000000000000000000000000000000000 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { OrganisationsEinheitenE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten.e2e.component'; -import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; -import { exist } from '../../../support/cypress.util'; -import { OrganisationsEinheitSyncResultE2E } from '../../../support/organisationseinheit'; -import { - createBauamtOrganisationsEinheit, - createDenkmalpflegeOrganisationsEinheit, - createFundstelleOrganisationsEinheit, - initOrganisationsEinheiten, -} from '../../../support/organisationseinheit-util'; -import { loginAsAriane } from '../../../support/user-util'; - -describe.skip('TODO: activate after fix for OZG-7391: show Organisationsheiten', () => { - const mainPage: MainPage = new MainPage(); - const organisationsEinheitenTab: OrganisationsEinheitenE2EComponent = new OrganisationsEinheitenE2EComponent(); - - before(() => { - loginAsAriane(); - initOrganisationsEinheiten([ - createBauamtOrganisationsEinheit(), - { ...createDenkmalpflegeOrganisationsEinheit(), syncResult: OrganisationsEinheitSyncResultE2E.NAME_MISMATCH }, - { ...createFundstelleOrganisationsEinheit(), syncResult: OrganisationsEinheitSyncResultE2E.NOT_FOUND_IN_PVOG }, - ]); - }); - - it('should show table of Organisationseinheiten', () => { - waitForSpinnerToDisappear(); - mainPage.clickOrganisationsEinheitenNavigationItem(); - - exist(organisationsEinheitenTab.getOrganisationsEinheitList()); - }); - - it('should show identical data in Keycloak and PVOG without error', () => { - organisationsEinheitenTab.getListItemByName('Bauamt'); - organisationsEinheitenTab.organisationsEinheitContainsID('Bauamt', '248240886'); - organisationsEinheitenTab.elementShowsNoError('Bauamt'); - }); - - it('should show data not found in PVOG in red color and with error icon and tooltip message', () => { - const organisationsEinheitName: string = 'Fundstelle'; - - organisationsEinheitenTab.organisationsEinheitContainsID(organisationsEinheitName, '10363455'); - organisationsEinheitenTab.elementIsShownAsError(organisationsEinheitName); - organisationsEinheitenTab.getErrorIconInElement(organisationsEinheitName); - organisationsEinheitenTab.getErrorTooltip(organisationsEinheitName); - }); - - it('should get name for OE from PVOG, if mismatch with Keycloak, but without error', () => { - organisationsEinheitenTab.organisationsEinheitContainsID('Landesamt für Denkmalpflege', '9093371'); - organisationsEinheitenTab.elementShowsNoError('Landesamt für Denkmalpflege'); - }); -}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-signaturen.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-signaturen.cy.ts deleted file mode 100644 index 1e5a19499a0ad62ca94e6cbf43aff7c18856f6d8..0000000000000000000000000000000000000000 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-signaturen.cy.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { OrganisationseinheitenSignaturE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten-signatur.e2e.component'; -import { OrganisationsEinheitenE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten.e2e.component'; -import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; -import { haveText } from '../../../support/cypress.util'; -import { loginAsAriane } from '../../../support/user-util'; - -describe.skip('TODO: activate after fix for OZG-7391: Signatur', () => { - const mainPage: MainPage = new MainPage(); - const signaturePage: OrganisationseinheitenSignaturE2EComponent = new OrganisationseinheitenSignaturE2EComponent(); - const organisationsEinheitenTab: OrganisationsEinheitenE2EComponent = new OrganisationsEinheitenE2EComponent(); - - const signatureText: string = 'Signatur\nmit\n\n\n\nZeilenumbruch\n\n'; - - before(() => { - loginAsAriane(); - }); - - it('should open signature page for Bauamt on click', () => { - waitForSpinnerToDisappear(); - mainPage.openOrganisationsEinheiten(); - organisationsEinheitenTab.openOrganisationsEinheit('Bauamt'); - - haveText(signaturePage.getOrganisationsEinheitName(), 'Bauamt'); - }); - - it('should show signature input with scrollbar', () => { - signaturePage.setSignature(signatureText); - signaturePage.saveSignature(); - - signaturePage.hasSignature(signatureText); - signaturePage.hasScrollbar(); - }); - - it.skip('should enter and save empty signature', () => { - signaturePage.clearSignature(); - signaturePage.saveSignature(); - - signaturePage.hasSignature(''); - }); -}); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/postfach/postfach-signatur.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/postfach/postfach-signatur.cy.ts index 1a8b4a69f8b1f19d1c2daf27b65274f46fea62e0..779a88f3c46a4724f9d188768e7344e9b992cf1d 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/postfach/postfach-signatur.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/postfach/postfach-signatur.cy.ts @@ -21,12 +21,12 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { E2EPostfachHelper } from 'apps/admin-e2e/src/helper/postfach/postfach.helper'; import { PostfachE2EComponent } from '../../../components/postfach/postfach.e2e.component'; -import { waitForSpinnerToDisappear } from '../../../page-objects/main.po'; -import { exist } from '../../../support/cypress.util'; import { loginAsAriane } from '../../../support/user-util'; -describe('Signatur', () => { +describe('(TODO: Ist noch wackelig in Bezug auf die Eingabe in das Feld) Postfach Signatur', () => { + const postfachHelper: E2EPostfachHelper = new E2EPostfachHelper(); const postfach: PostfachE2EComponent = new PostfachE2EComponent(); const signaturText: string = 'Signatur\nmit\n\n\n\nZeilenumbruch\n\n'; @@ -35,12 +35,9 @@ describe('Signatur', () => { loginAsAriane(); }); - it('should show Postfach page', () => { - waitForSpinnerToDisappear(); - exist(postfach.getSignaturText()); - }); - it('should show signature input with scrollbar', () => { + postfachHelper.openPostfachPage(); + postfach.setSignatur(signaturText); postfach.saveSignatur(); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/statistik/statistik-fields.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/statistik/statistik-fields.cy.ts index dae58525a8d2522f3c0a241f5f24423cba928f4b..d877ae4b900417af37328322176ce8070c6d0844 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/statistik/statistik-fields.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/statistik/statistik-fields.cy.ts @@ -1,40 +1,30 @@ -import { ROUTES } from '@admin-client/shared'; +import { StatistikE2EComponent } from 'apps/admin-e2e/src/components/statistik/statistik.e2e.component'; import { StatistikFieldsFormE2EComponent } from '../../../components/statistik/statistik-fields-form.e2e.component'; -import { StatistikE2EComponent } from '../../../components/statistik/statistik.e2e.component'; -import { urlShouldEndsWith } from '../../../support/cypress-helper'; -import { exist } from '../../../support/cypress.util'; +import { exist, haveText, haveValue } from '../../../support/cypress.util'; import { loginAsDaria } from '../../../support/user-util'; describe('Felder in Statistik hinzufügen', () => { const component: StatistikE2EComponent = new StatistikE2EComponent(); const fieldsFormComponent: StatistikFieldsFormE2EComponent = new StatistikFieldsFormE2EComponent(); + const dataText1: string = 'Eingabe A'; + const dataText2: string = 'Eingabe B'; + before(() => { loginAsDaria(); }); - it('should be on statistik page', () => { - urlShouldEndsWith(ROUTES.STATISTIK); - }); - - it('should show statistik header', () => { - component.isHeaderTextVisible(); + it('should show "Weitere Felder auswerten" button', () => { + exist(component.getWeitereFelderAuswertenButton()); }); - it('should show "Weiter Felder auswerten" button', () => { - exist(component.getWeiterFelderAuswertenButton()); - }); - - it('should navigate to route', () => { - component.getWeiterFelderAuswertenButton().click(); - - urlShouldEndsWith(ROUTES.STATISTIK_NEU); - }); + it('should have all form elements after button click', () => { + component.getWeitereFelderAuswertenButton().click(); - it('should have all form elements', () => { exist(fieldsFormComponent.getFormEngineInput()); exist(fieldsFormComponent.getFormIdInput()); exist(fieldsFormComponent.getDataFieldInput(0)); + exist(fieldsFormComponent.getDataFieldDeleteButton(0)); exist(fieldsFormComponent.getAddFieldButton()); exist(fieldsFormComponent.getSaveButton()); exist(fieldsFormComponent.getCancelButton()); @@ -46,9 +36,22 @@ describe('Felder in Statistik hinzufügen', () => { exist(fieldsFormComponent.getDataFieldInput(1)); }); + it('should enter text in both data fields', () => { + fieldsFormComponent.enterDataFieldPath(dataText1, 0); + fieldsFormComponent.enterDataFieldPath(dataText2, 1); + + haveValue(fieldsFormComponent.getDataFieldInput(0), dataText1); + haveValue(fieldsFormComponent.getDataFieldInput(1), dataText2); + }); + + it('should delete data fields', () => { + fieldsFormComponent.deleteDataField(0); + haveValue(fieldsFormComponent.getDataFieldInput(0), dataText2); + }); + it('should navigate to statistik on cancel', () => { fieldsFormComponent.cancel(); - urlShouldEndsWith(ROUTES.STATISTIK); + exist(component.getWeitereFelderAuswertenButton()); }); }); diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts new file mode 100644 index 0000000000000000000000000000000000000000..f0ed339c63bbb596e72915d346b97c8390bf35c1 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts @@ -0,0 +1,61 @@ +import { + BenutzerDeleteDialogE2EComponent, + BenutzerE2EComponent, + BenutzerListE2EComponent, +} from '../../components/benutzer/benutzer.e2e.component'; +import { SnackBarE2EComponent } from '../../components/ui/snackbar.e2e.component'; +import { OrganisationsEinheitE2E } from '../../model/organisations-einheit'; +import { AdminUserE2E } from '../../model/util'; +import { exist, notExist } from '../../support/cypress.util'; + +export class E2EBenutzerExecutor { + private benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); + private snackBar: SnackBarE2EComponent = new SnackBarE2EComponent(); + private benutzerDeleteDialog: BenutzerDeleteDialogE2EComponent = new BenutzerDeleteDialogE2EComponent(); + private benutzerListPage: BenutzerListE2EComponent = new BenutzerListE2EComponent(); + + public modifyBenutzer(user: AdminUserE2E): void { + this.benutzerPage.getVornameInput().type(user.vorname); + this.benutzerPage.getNachnameInput().type(user.nachname); + this.benutzerPage.getBenutzernameInput().type(user.username); + this.benutzerPage.getMailInput().type(user.email); + + if (user.isAdmin) { + this.benutzerPage.getAdminCheckbox().getRoot().click(); + } + if (user.isUser) { + this.benutzerPage.getUserCheckbox().getRoot().click(); + } + if (user.isLoeschen) { + this.benutzerPage.getLoeschenCheckbox().getRoot().click(); + } + if (user.isPoststelle) { + this.benutzerPage.getPostCheckbox().getRoot().click(); + } + + this.modifyOrganisationsEinheiten(user.organisationseinheiten); + } + + public modifyOrganisationsEinheiten(organisationseinheiten: OrganisationsEinheitE2E[]): void { + organisationseinheiten.forEach((name: string) => this.benutzerPage.getOrganisationsEinheitCheckbox(name).click()); + } + + public saveAndCloseSnackbar(): void { + this.saveBenutzer(); + exist(this.snackBar.getMessage()); + this.snackBar.getCloseButton().click(); + notExist(this.snackBar.getMessage()); + exist(this.benutzerListPage.getList()); + } + + public saveBenutzer(): void { + this.benutzerPage.getSaveButton().click(); + } + + public deleteBenutzer(): void { + this.benutzerPage.getDeleteButton().click(); + exist(this.benutzerDeleteDialog.getDeleteButton()); + this.benutzerDeleteDialog.getDeleteButton().click(); + exist(this.benutzerListPage.getList()); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b06766322f460cdb12f6ec065e19b9e79e756f0 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts @@ -0,0 +1,61 @@ +import { OrganisationsEinheitE2E } from '../../model/organisations-einheit'; +import { AdminUserE2E } from '../../model/util'; +import { E2EBenutzerExecutor } from './benutzer.executor'; +import { E2EBenutzerNavigator } from './benutzer.navigator'; + +export class E2EBenutzerHelper { + private navigator: E2EBenutzerNavigator = new E2EBenutzerNavigator(); + private executer: E2EBenutzerExecutor = new E2EBenutzerExecutor(); + + public openBenutzerListPage(): void { + this.navigator.openBenutzerListPage(); + } + + public openNewBenutzerPage(): void { + this.navigator.openNewBenutzerPage(); + } + + public addBenutzerAndSave(user: AdminUserE2E): void { + this.addBenutzer(user); + this.saveAndCloseSnackbar(); + } + + public addBenutzer(user: AdminUserE2E): void { + this.modifyBenutzer(user); + } + + public editBenutzerAndSave(user: AdminUserE2E): void { + this.editBenutzer(user); + this.saveAndCloseSnackbar(); + } + + public editBenutzer(user: AdminUserE2E): void { + this.modifyBenutzer(user); + } + + private modifyBenutzer(user: AdminUserE2E): void { + this.executer.modifyBenutzer(user); + } + + public editOrganisationsEinheitenAndSave(organisationsEinheiten: OrganisationsEinheitE2E[]): void { + this.executer.modifyOrganisationsEinheiten(organisationsEinheiten); + this.saveAndCloseSnackbar(); + } + + private saveAndCloseSnackbar(): void { + this.executer.saveAndCloseSnackbar(); + } + + public saveBenutzer(): void { + this.executer.saveBenutzer(); + } + + public deleteBenutzer(userName: string): void { + this.openBenutzerPage(userName); + this.executer.deleteBenutzer(); + } + + public openBenutzerPage(userName: string): void { + this.navigator.openBenutzerPage(userName); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.navigator.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.navigator.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc5eee020b1931cd7dfa074c9ae070e7c6fe5121 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.navigator.ts @@ -0,0 +1,32 @@ +import { BenutzerE2EComponent, BenutzerListE2EComponent } from '../../components/benutzer/benutzer.e2e.component'; +import { MainPage } from '../../page-objects/main.po'; +import { exist } from '../../support/cypress.util'; + +export class E2EBenutzerNavigator { + private mainPage: MainPage = new MainPage(); + private benutzerListPage: BenutzerListE2EComponent = new BenutzerListE2EComponent(); + private benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); + + public openNewBenutzerPage(): void { + this.openBenutzerListPage(); + + this.benutzerListPage.getHinzufuegenButton().click(); + exist(this.benutzerPage.getHeadline()); + } + + public openBenutzerPage(userName: string) { + this.openBenutzerListPage(); + this.benutzerListPage.getItem(userName).getRoot().click(); + exist(this.benutzerPage.getHeadline()); + } + + public openBenutzerListPage(): void { + this.navigateToDomain(); + this.mainPage.getBenutzerNavigationItem().click(); + exist(this.benutzerListPage.getHeadline()); + } + + private navigateToDomain(): void { + this.mainPage.getHeader().getLogo().click(); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts new file mode 100644 index 0000000000000000000000000000000000000000..be750e3dfbfe90c3821451932ecb475bd31cbbe5 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts @@ -0,0 +1,51 @@ +import { + BenutzerE2EComponent, + BenutzerListE2EComponent, + BenutzerListItemE2EComponent, +} from '../../components/benutzer/benutzer.e2e.component'; +import { AdminUserE2E } from '../../model/util'; +import { contains, exist, notExist } from '../../support/cypress.util'; +import { AlfaRollen } from '../../support/user-util'; + +export class E2EBenutzerVerifier { + private noOrganisationsEinheitAssignedText: string = 'keine zuständige Stelle zugewiesen'; + + private benutzerListPage: BenutzerListE2EComponent = new BenutzerListE2EComponent(); + private benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent(); + + public verfiyOrganisationsEinheitInBenutzerList(organisationsEinheit: string, userName: string): void { + const benutzerItem: BenutzerListItemE2EComponent = this.getBenutzerItem(userName); + contains(benutzerItem.getOrganisationsEinheiten(), organisationsEinheit); + } + + public verifyNoOrganisationsEinheitInBenutzerList(userName: string): void { + const benutzerItem: BenutzerListItemE2EComponent = this.getBenutzerItem(userName); + contains(benutzerItem.getNoOrganisationsEinheitText(), this.noOrganisationsEinheitAssignedText); + } + + public verifyOrganisationsEinheitenInFormular(organisationsEinheiten: string[]): void { + organisationsEinheiten.forEach((organisationsEinheit) => { + exist(this.benutzerPage.getOrganisationsEinheitCheckbox(organisationsEinheit)); + }); + } + + public verifyUserInList(user: AdminUserE2E): void { + const benutzer: BenutzerListItemE2EComponent = this.getBenutzerItem(user.username); + contains(benutzer.getUserName(), user.username); + contains(benutzer.getFullName(), user.vorname); + contains(benutzer.getFullName(), user.nachname); + + if (user.isUser) contains(benutzer.getRoles(), AlfaRollen.USER); + if (user.isLoeschen) contains(benutzer.getRoles(), AlfaRollen.LOESCHEN); + if (user.isPoststelle) contains(benutzer.getRoles(), AlfaRollen.POSTSTELLE); + if (user.isAdmin) contains(benutzer.getRoles(), AlfaRollen.ADMIN); + } + + public verifyUserNotInList(userName: string): void { + notExist(this.getBenutzerItem(userName).getRoot()); + } + + private getBenutzerItem(userName: string): BenutzerListItemE2EComponent { + return this.benutzerListPage.getItem(userName); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.executor.ts b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.executor.ts new file mode 100644 index 0000000000000000000000000000000000000000..a89ad1f958ca17a73511980431455f6e8ae479bc --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.executor.ts @@ -0,0 +1,34 @@ +import { + OrganisationsEinheitDeleteDialogE2EComponent, + OrganisationsEinheitListE2EComponent, +} from '../../components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { ZustaendigeStelleDialogE2EComponent } from '../../components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component'; +import { OrganisationsEinheitPage } from '../../page-objects/organisations-einheit.po'; +import { exist } from '../../support/cypress.util'; +import { convertToDataTestId } from '../../support/tech-util'; + +export class E2EOrganisationsEinheitExecutor { + private readonly organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + private readonly organisationsEinheitList: OrganisationsEinheitListE2EComponent = this.organisationsEinheitPage.getList(); + + private readonly deleteDialog: OrganisationsEinheitDeleteDialogE2EComponent = + new OrganisationsEinheitDeleteDialogE2EComponent(); + + private readonly zustaendigeStelleSearchComponent: ZustaendigeStelleDialogE2EComponent = + new ZustaendigeStelleDialogE2EComponent(); + + public addOrganisationsEinheit(name: string): void { + //TODO von index auf Name umstellen + this.zustaendigeStelleSearchComponent + .getZustaendigeStelleTitle(0) + .then((name: string) => this.zustaendigeStelleSearchComponent.clickFoundItem(0)); + exist(this.organisationsEinheitList.getRoot()); + } + + public deleteOrganisationsEinheit(name: string): void { + this.organisationsEinheitList.getListItem(convertToDataTestId(name)).getDeleteButton().click(); + exist(this.deleteDialog.getRoot()); + this.deleteDialog.getDeleteButton().click(); + exist(this.organisationsEinheitList.getRoot()); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper.ts b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..339793cde888c3cedcbd1dc6e4dd3b7a7f28e313 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.helper.ts @@ -0,0 +1,19 @@ +import { E2EOrganisationsEinheitExecutor } from './organisations-einheit.executor'; +import { E2EOrganisationsEinheitNavigator } from './organisations-einheit.navigator'; + +export class E2EOrganisationsEinheitHelper { + private readonly navigator: E2EOrganisationsEinheitNavigator = new E2EOrganisationsEinheitNavigator(); + private readonly executor: E2EOrganisationsEinheitExecutor = new E2EOrganisationsEinheitExecutor(); + + public openOrganisationsEinheitPage(): void { + this.navigator.openOrganisationsEinheitListPage(); + } + + public addOrganisationsEinheit(name: string): void { + this.executor.addOrganisationsEinheit(name); + } + + public deleteOrganisationsEinheit(name: string): void { + this.executor.deleteOrganisationsEinheit(name); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.navigator.ts b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.navigator.ts new file mode 100644 index 0000000000000000000000000000000000000000..6274ee80d4bbbf2d2326f685d83d84da6e1ea07e --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.navigator.ts @@ -0,0 +1,21 @@ +import { OrganisationsEinheitListE2EComponent } from '../../components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { MainPage } from '../../page-objects/main.po'; +import { OrganisationsEinheitPage } from '../../page-objects/organisations-einheit.po'; +import { exist } from '../../support/cypress.util'; + +export class E2EOrganisationsEinheitNavigator { + private readonly mainPage: MainPage = new MainPage(); + + private readonly organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + private readonly organisationsEinheitList: OrganisationsEinheitListE2EComponent = this.organisationsEinheitPage.getList(); + + public openOrganisationsEinheitListPage(): void { + this.navigateToDomain(); + this.mainPage.getOrganisationEinheitNavigationItem().click(); + exist(this.organisationsEinheitList.getRoot()); + } + + private navigateToDomain(): void { + this.mainPage.getHeader().getLogo().click(); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.verifier.ts b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.verifier.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb1b43021649ffea97c252237556966d3dea38ac --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/organisations-einheit/organisations-einheit.verifier.ts @@ -0,0 +1,17 @@ +import { OrganisationsEinheitListE2EComponent } from '../../components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { OrganisationsEinheitPage } from '../../page-objects/organisations-einheit.po'; +import { exist, notExist } from '../../support/cypress.util'; +import { convertToDataTestId } from '../../support/tech-util'; + +export class E2EOrganisationsEinheitVerifier { + private readonly organisationsEinheitPage: OrganisationsEinheitPage = new OrganisationsEinheitPage(); + private readonly organisationsEinheitList: OrganisationsEinheitListE2EComponent = this.organisationsEinheitPage.getList(); + + public verifyOrganisationsEinheitInList(name: string): void { + exist(this.organisationsEinheitList.getListItem(convertToDataTestId(name)).getRoot()); + } + + public verifyOrganisationsEinheitNotInList(name: string): void { + notExist(this.organisationsEinheitList.getListItem(convertToDataTestId(name)).getRoot()); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/postfach/postfach.helper.ts b/alfa-client/apps/admin-e2e/src/helper/postfach/postfach.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..90624b0151bdae26e5e1859734132d6b2e24cf62 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/postfach/postfach.helper.ts @@ -0,0 +1,9 @@ +import { E2EPostfachNavigator } from './postfach.navigator'; + +export class E2EPostfachHelper { + private readonly navigator: E2EPostfachNavigator = new E2EPostfachNavigator(); + + public openPostfachPage(): void { + this.navigator.openPostfachPage(); + } +} diff --git a/alfa-client/apps/admin-e2e/src/helper/postfach/postfach.navigator.ts b/alfa-client/apps/admin-e2e/src/helper/postfach/postfach.navigator.ts new file mode 100644 index 0000000000000000000000000000000000000000..074438baaaf22cc22669f085412ddd5d2f22046d --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/helper/postfach/postfach.navigator.ts @@ -0,0 +1,18 @@ +import { PostfachE2EComponent } from '../../components/postfach/postfach.e2e.component'; +import { MainPage } from '../../page-objects/main.po'; +import { exist } from '../../support/cypress.util'; + +export class E2EPostfachNavigator { + private mainPage: MainPage = new MainPage(); + private postfach: PostfachE2EComponent = new PostfachE2EComponent(); + + public openPostfachPage(): void { + this.navigateToDomain(); + this.mainPage.getPostfachNavigationItem().click(); + exist(this.postfach.getHeadline()); + } + + private navigateToDomain(): void { + this.mainPage.getHeader().getLogo().click(); + } +} diff --git a/alfa-client/apps/admin-e2e/src/model/organisations-einheit.ts b/alfa-client/apps/admin-e2e/src/model/organisations-einheit.ts new file mode 100644 index 0000000000000000000000000000000000000000..311c1bfd5c4f39e55d5ddf17a4f91fdf34f19067 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/model/organisations-einheit.ts @@ -0,0 +1,7 @@ +export enum OrganisationsEinheitE2E { + BAUAMT = 'Bauamt', + DENKMALPFLEGE = 'Denkmalpflege', + FUNDSTELLE = 'Fundstelle', + ORDNUNGSAMT = 'Ordnungsamt', + WIRTSCHAFTSFOERDERUNG = 'Wirtschaftsförderung', +} diff --git a/alfa-client/apps/admin-e2e/src/model/util.ts b/alfa-client/apps/admin-e2e/src/model/util.ts index a6e185610f221306000e326517a8906ecb1c1f6f..7ad5cd61ab1741c43e02532c38b711c92eb9c8dc 100644 --- a/alfa-client/apps/admin-e2e/src/model/util.ts +++ b/alfa-client/apps/admin-e2e/src/model/util.ts @@ -1,3 +1,5 @@ +import { OrganisationsEinheitE2E } from './organisations-einheit'; + /* * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein @@ -41,11 +43,12 @@ export enum HttpMethodE2E { export interface AdminUserE2E { vorname: string; nachname: string; - benutzername: string; + username: string; email: string; isAdmin?: boolean; + isDatenbeauftragung?: boolean; isUser?: boolean; isLoeschen?: boolean; isPoststelle?: boolean; - organisationseinheiten?: string[]; + organisationseinheiten: OrganisationsEinheitE2E[]; } diff --git a/alfa-client/apps/admin-e2e/src/page-objects/header.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/header.po.ts index fb87e566cab2f09fd01b593fa883841fc6075243..60fc24b0d4bf09419e0f0e0b0e15f3b439d2f500 100644 --- a/alfa-client/apps/admin-e2e/src/page-objects/header.po.ts +++ b/alfa-client/apps/admin-e2e/src/page-objects/header.po.ts @@ -24,13 +24,13 @@ import { CurrentUserProfileE2EComponent } from '../components/user-profile/current-user-profile.component.e2e'; import { UserSettingsE2EComponent } from '../components/user-settings/user-settings.component.e2e'; +//TODO Zu den Componenten packen, nicht zu den page-objects export class HeaderE2EComponent { private readonly locatorLogo: string = 'logo-link'; private readonly locatorRoot: string = 'header'; private readonly userSettings: UserSettingsE2EComponent = new UserSettingsE2EComponent(); - private readonly currentUserProfile: CurrentUserProfileE2EComponent = - new CurrentUserProfileE2EComponent(); + private readonly currentUserProfile: CurrentUserProfileE2EComponent = new CurrentUserProfileE2EComponent(); public getRoot() { return cy.getTestElement(this.locatorRoot); diff --git a/alfa-client/apps/admin-e2e/src/page-objects/login.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/login.po.ts index 73005caaf69cb0be2990541fdabe003326a20153..48a03c246ceeeea3b8b7e58c3f211e091c86e955 100644 --- a/alfa-client/apps/admin-e2e/src/page-objects/login.po.ts +++ b/alfa-client/apps/admin-e2e/src/page-objects/login.po.ts @@ -1,3 +1,4 @@ +//TODO Das sollte eher eine Component als eine Page sein export class LoginPage { private readonly locatorLogin: string = '#kc-login'; private readonly locatorBarrierefreiheitLink: string = '#kc-barrierefreiheit'; diff --git a/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts index 338c98286a6f1fc02e97d52381b99de0dbfc3db6..75ecbdaefa5d692ca637af325afb58ec05f80abe 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 @@ -22,17 +22,16 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BuildInfoE2EComponent } from '../components/buildinfo/buildinfo.e2e.component'; -import { containClass, exist } from '../support/cypress.util'; import { HeaderE2EComponent } from './header.po'; export class MainPage { private readonly buildInfo: BuildInfoE2EComponent = new BuildInfoE2EComponent(); private readonly header: HeaderE2EComponent = new HeaderE2EComponent(); - private readonly benutzerNavigationItem: string = 'caption-Benutzer__Rollen'; - private readonly postfachNavigationItem: string = 'postfach-navigation'; - private readonly organisationEinheitNavigationItem: string = 'organisations-einheiten-navigation'; - private readonly statistikNavigationItem: string = 'statistik-navigation'; + private readonly benutzerNavigationItem: string = 'link-path-benutzer'; + private readonly organisationEinheitNavigationItem: string = 'link-path-organisationseinheiten'; + private readonly postfachNavigationItem: string = 'link-path-postfach'; + private readonly statistikNavigationItem: string = 'link-path-statistik'; public getBuildInfo(): BuildInfoE2EComponent { return this.buildInfo; @@ -46,41 +45,17 @@ export class MainPage { return cy.getTestElement(this.benutzerNavigationItem); } - public clickBenutzerNavigationItem(): void { - this.getBenutzerNavigationItem().click(); - } - - public benutzerNavigationItemIsVisible(): void { - exist(this.getBenutzerNavigationItem()); - } - public getPostfachNavigationItem(): Cypress.Chainable<Element> { return cy.getTestElement(this.postfachNavigationItem); } - public clickPostfachNavigationItem(): void { - this.getPostfachNavigationItem().click(); - } - public getOrganisationEinheitNavigationItem(): Cypress.Chainable<Element> { return cy.getTestElement(this.organisationEinheitNavigationItem); } - public clickOrganisationsEinheitenNavigationItem(): void { - this.getOrganisationEinheitNavigationItem().click(); - } - public getStatistikNavigationItem(): Cypress.Chainable<Element> { return cy.getTestElement(this.statistikNavigationItem); } - - public clickStatistikNavigationItem(): void { - this.getStatistikNavigationItem().click(); - } - - public isStatistikNavigationItemSelected(): void { - containClass(this.getStatistikNavigationItem().get('a'), 'border-selected'); - } } export function waitForSpinnerToDisappear(): boolean { diff --git a/alfa-client/apps/admin-e2e/src/page-objects/organisations-einheit.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/organisations-einheit.po.ts new file mode 100644 index 0000000000000000000000000000000000000000..b37d9a9d3f0cd1cafc518f2ae995b0988807c7e9 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/page-objects/organisations-einheit.po.ts @@ -0,0 +1,14 @@ +import { OrganisationsEinheitListE2EComponent } from '../components/organisationseinheiten/organisationseinheiten.e2e.component'; + +export class OrganisationsEinheitPage { + private readonly addButton: string = 'add-organisationseinheit-button'; + private readonly list: OrganisationsEinheitListE2EComponent = new OrganisationsEinheitListE2EComponent(); + + public getAddButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.addButton); + } + + public getList(): OrganisationsEinheitListE2EComponent { + return this.list; + } +} diff --git a/alfa-client/apps/admin-e2e/src/support/cypress.util.ts b/alfa-client/apps/admin-e2e/src/support/cypress.util.ts index 505762fe8f5193921ee7b5bffb76234f5b21393b..63f3c24872ebea08644aa85eead331dac07f1b40 100644 --- a/alfa-client/apps/admin-e2e/src/support/cypress.util.ts +++ b/alfa-client/apps/admin-e2e/src/support/cypress.util.ts @@ -60,10 +60,6 @@ export function mouseEnter(element: Cypress.Chainable<Element>): void { element.trigger('mouseenter'); } -export function mouseOver(element: Cypress.Chainable<Element>): void { - element.trigger('mouseover'); -} - export function contains(element: Cypress.Chainable<Element>, containing: string): void { element.should('exist').contains(containing); } @@ -122,16 +118,12 @@ 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<Element>, value: string, delayBeforeEnter: number = 200): void { element.clear().type(value); wait(delayBeforeEnter); element.type(CypressKeyboardActions.ENTER); } -export function typeText(element: Cypress.Chainable<JQuery<HTMLElement>>, value: string): void { - element.type(value); -} - export function backspaceOn(element: Cypress.Chainable<Element>): void { element.type(CypressKeyboardActions.BACKSPACE); } diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.scss b/alfa-client/apps/admin-e2e/src/support/linkrels.ts similarity index 92% rename from alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.scss rename to alfa-client/apps/admin-e2e/src/support/linkrels.ts index 54c4f3eb8c92af93694c03cdf577fed23cf9f86b..4891aa61e1bdf2e6bcc77f167e7aa6a7fd7aedb1 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.scss +++ b/alfa-client/apps/admin-e2e/src/support/linkrels.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei @@ -21,3 +21,6 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +export enum ApiRootLinkRelE2E { + DOCUMENTATIONS = 'documentations', +} diff --git a/alfa-client/apps/admin-e2e/src/support/tech-util.ts b/alfa-client/apps/admin-e2e/src/support/tech-util.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e6c57d156a011e83fe53bee52ad26a56a384ad2 --- /dev/null +++ b/alfa-client/apps/admin-e2e/src/support/tech-util.ts @@ -0,0 +1,15 @@ +export function convertToDataTestId(value: string): string { + if (value == null || value == undefined) { + return ''; + } + value = replaceAllWhitespaces(value, '_'); + return simpleTransliteration(value); +} + +export function replaceAllWhitespaces(value: string, replaceWith: string): string { + return value.replace(/ /g, replaceWith); +} + +export function simpleTransliteration(value: string): string { + return value.normalize('NFKD').replace(/[^-A-Za-z0-9_]/g, ''); +} diff --git a/alfa-client/apps/admin-e2e/src/support/user-util.ts b/alfa-client/apps/admin-e2e/src/support/user-util.ts index 46f07823c27eee9c7872cc8f72a39a33a5326548..989e061babc526985e62ace05e516bdb2d058516 100644 --- a/alfa-client/apps/admin-e2e/src/support/user-util.ts +++ b/alfa-client/apps/admin-e2e/src/support/user-util.ts @@ -23,7 +23,8 @@ */ import { UserE2E } from '../model/user'; import { HeaderE2EComponent } from '../page-objects/header.po'; -import { MainPage } from '../page-objects/main.po'; +import { MainPage, waitForSpinnerToDisappear } from '../page-objects/main.po'; +import { wait } from './cypress-helper'; export function loginAsAriane(): void { login(UserJsonPath.ARIANE); @@ -43,6 +44,8 @@ export function loginAsSafira(): void { function login(userJson: string): void { cy.fixture(userJson).then((user) => loginByUi(user)); + waitForSpinnerToDisappear(); + wait(3000, 'Wait for initial navigation is done'); } // Hinweis: cacheAcrossSpecs: true lässt Tests umfallen @@ -85,10 +88,13 @@ export enum AlfaRollen { USER = 'VERWALTUNG_USER', LOESCHEN = 'VERWALTUNG_LOESCHEN', POSTSTELLE = 'VERWALTUNG_POSTSTELLE', - ADMIN = 'ADMIN_ADMIN' + ADMIN = 'ADMIN_ADMIN', + DATEN = 'DATENBEAUFTRAGUNG', } export enum AlfaUsers { + ARIANE = 'ariane', + DARIA = 'daria', DOROTHEA = 'dorothea', LUDWIG = 'ludwig', PETER = 'peter', diff --git a/alfa-client/apps/admin/package.json b/alfa-client/apps/admin/package.json index 924f232583709ebf8224d6a8301cd00d84cb1307..d124f972122f91564b8d04044ab8541beb8b4ae6 100644 --- a/alfa-client/apps/admin/package.json +++ b/alfa-client/apps/admin/package.json @@ -1,4 +1,4 @@ { "name": "admin", - "version": "1.6.0-SNAPSHOT" + "version": "1.7.0-SNAPSHOT" } diff --git a/alfa-client/apps/admin/src/app/app.component.html b/alfa-client/apps/admin/src/app/app.component.html index 8302d53018783e91ffb01539a627b9e5ee7730ff..55e1c8824521c5c846932ee5d648172d86466705 100644 --- a/alfa-client/apps/admin/src/app/app.component.html +++ b/alfa-client/apps/admin/src/app/app.component.html @@ -36,7 +36,10 @@ > <ods-admin-logo-icon /> </a> - <user-profile-button-container data-test-id="user-profile-button"></user-profile-button-container> + <user-profile-button-container + [apiRootStateResource]="apiRootStateResource$ | async" + data-test-id="user-profile-button" + ></user-profile-button-container> </header> <div class="flex h-screen w-full justify-center overflow-y-auto"> <ods-navbar data-test-id="navigation"> diff --git a/alfa-client/apps/admin/src/app/app.component.spec.ts b/alfa-client/apps/admin/src/app/app.component.spec.ts index 34bd937efea886d40f3dc6f77df5eb1b3d68bce5..baa8c7fcc532a0e859fe596782a94bf7b2257803 100644 --- a/alfa-client/apps/admin/src/app/app.component.spec.ts +++ b/alfa-client/apps/admin/src/app/app.component.spec.ts @@ -27,31 +27,18 @@ import { KeycloakTokenService } from '@admin/keycloak-shared'; import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared'; import { BuildInfoComponent } from '@alfa-client/common'; import { createEmptyStateResource, createStateResource, HasLinkPipe } from '@alfa-client/tech-shared'; -import { - existsAsHtmlElement, - getElementComponentFromFixtureByCss, - Mock, - mock, - notExistsAsHtmlElement, -} from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getElementComponentFromFixtureByCss, Mock, mock, notExistsAsHtmlElement, } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { AuthenticationService } from '@authentication'; -import { - AdminLogoIconComponent, - MailboxIconComponent, - NavbarComponent, - NavItemComponent, - OrgaUnitIconComponent, - UsersIconComponent, -} from '@ods/system'; +import { AdminLogoIconComponent, MailboxIconComponent, NavbarComponent, NavItemComponent, OrgaUnitIconComponent, UsersIconComponent, } from '@ods/system'; import { createConfigurationResource } from 'libs/admin/configuration-shared/test/configuration'; import { MenuContainerComponent } from 'libs/admin/configuration/src/lib/menu-container/menu-container.component'; import { createApiRootResource } from 'libs/api-root-shared/test/api-root'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent, MockDirective } from 'ng-mocks'; import { of, Subscription } from 'rxjs'; -import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component'; +import { UserProfileButtonContainerComponent } from '../../../../libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component'; import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component'; import { AppComponent } from './app.component'; @@ -68,29 +55,31 @@ describe('AppComponent', () => { const routerOutletSelector: string = getDataTestIdOf('router-outlet'); const menuContainer: string = getDataTestIdOf('menu-container'); - const authenticationService: Mock<AuthenticationService> = { - ...mock(AuthenticationService), - login: jest.fn().mockResolvedValue(Promise.resolve()), - }; - - 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); + let authenticationService: Mock<AuthenticationService>; + let router: Mock<Router>; + let route: Mock<ActivatedRoute>; + let apiRootService: Mock<ApiRootService>; let configurationService: Mock<ConfigurationService>; let keycloakTokenService: Mock<KeycloakTokenService>; beforeEach(async () => { + authenticationService = { + ...mock(AuthenticationService), + login: jest.fn().mockResolvedValue(Promise.resolve()), + }; + router = mock(Router); + route = { + ...mock(ActivatedRoute), + snapshot: { + queryParams: { + iss: 'some-iss', + state: 'some-state', + session_state: 'some-session-state', + code: 'some-code', + }, + } as any, + }; + apiRootService = mock(ApiRootService); configurationService = mock(ConfigurationService); keycloakTokenService = mock(KeycloakTokenService); @@ -158,81 +147,110 @@ describe('AppComponent', () => { expect(authenticationService.login).toHaveBeenCalled(); }); - it('should call doAfterLoggedIn', async () => { - component.doAfterLoggedIn = jest.fn(); + it('should call doAfterLoggedIn only once', async () => { + component._doAfterLoggedIn = jest.fn(); component.ngOnInit(); await fixture.whenStable(); - expect(component.doAfterLoggedIn).toHaveBeenCalled(); + expect(component._doAfterLoggedIn).toHaveBeenCalledTimes(1); }); }); describe('do after logged in', () => { beforeEach(() => { - component.evaluateInitialNavigation = jest.fn(); + component._evaluateInitialNavigation = jest.fn(); }); it('should call apiRootService to getApiRoot', () => { - component.doAfterLoggedIn(); + component._doAfterLoggedIn(); expect(apiRootService.getApiRoot).toHaveBeenCalled(); }); it('should call keycloak token service to register token provider', () => { - component.doAfterLoggedIn(); + component._doAfterLoggedIn(); expect(keycloakTokenService.registerAccessTokenProvider).toHaveBeenCalled(); }); it('should call evaluateInitialNavigation', () => { - component.doAfterLoggedIn(); + component._doAfterLoggedIn(); - expect(component.evaluateInitialNavigation).toHaveBeenCalled(); + expect(component._evaluateInitialNavigation).toHaveBeenCalled(); }); }); describe('evaluate initial navigation', () => { beforeEach(() => { - component.evaluateNavigationByApiRoot = jest.fn(); + component._evaluateNavigationByApiRoot = jest.fn(); + component._subscribeApiRootForEvaluation = jest.fn(); }); - it('should call evaluate navigation by apiRoot', () => { - const apiRootResource: ApiRootResource = createApiRootResource(); + it('should call router navigate', () => { + window.location.pathname = '/path'; + (router as any).url = '/path'; + + component._evaluateInitialNavigation(); + + expect(router.navigate).toHaveBeenCalledWith([window.location.pathname]); + }); + + it('should call subscribe api root evaluation if url starts with /?state', () => { + (router as any).url = '/?state=some-state'; + + component._evaluateInitialNavigation(); + + expect(component._subscribeApiRootForEvaluation).toHaveBeenCalled(); + }); + }); + + describe('subscribeApiRootForEvaluation', () => { + const apiRootResource: ApiRootResource = createApiRootResource(); + + beforeEach(() => { component.apiRootStateResource$ = of(createStateResource(apiRootResource)); + component._evaluateNavigationByApiRoot = jest.fn(); + }); - component.evaluateInitialNavigation(); + it('should set apiRootSubscription', () => { + component._subscribeApiRootForEvaluation(); + + expect(component.apiRootSubscription).toBeDefined(); + }); + + it('should call evaluate navigation by apiRoot', () => { + component._subscribeApiRootForEvaluation(); - expect(component.evaluateNavigationByApiRoot).toHaveBeenCalledWith(apiRootResource); + expect(component._evaluateNavigationByApiRoot).toHaveBeenCalledWith(apiRootResource); }); it('should NOT call evaluate navigation by apiRoot if resource is loading', () => { - component.apiRootStateResource$ = of(createEmptyStateResource<ApiRootResource>(true)); - component.evaluateNavigationByApiRoot = jest.fn(); + component.apiRootStateResource$ = of(createStateResource(apiRootResource, true)); - component.evaluateInitialNavigation(); + component._subscribeApiRootForEvaluation(); - expect(component.evaluateNavigationByApiRoot).not.toHaveBeenCalled(); + expect(component._evaluateNavigationByApiRoot).not.toHaveBeenCalled(); }); }); describe('evaluate navigation api root', () => { it('should evaluate navigation by configuration if link exists', () => { - component.evaluateNavigationByConfiguration = jest.fn(); + component._evaluateNavigationByConfiguration = jest.fn(); const apiRootResource: ApiRootResource = createApiRootResource([ApiRootLinkRel.CONFIGURATION]); - component.evaluateNavigationByApiRoot(apiRootResource); + component._evaluateNavigationByApiRoot(apiRootResource); - expect(component.evaluateNavigationByConfiguration).toHaveBeenCalled(); + expect(component._evaluateNavigationByConfiguration).toHaveBeenCalled(); }); it('should navigate by api root if link is missing', () => { - component.navigateByApiRoot = jest.fn(); + component._navigateByApiRoot = jest.fn(); const apiRootResource: ApiRootResource = createApiRootResource(); - component.evaluateNavigationByApiRoot(apiRootResource); + component._evaluateNavigationByApiRoot(apiRootResource); - expect(component.navigateByApiRoot).toHaveBeenCalledWith(apiRootResource); + expect(component._navigateByApiRoot).toHaveBeenCalledWith(apiRootResource); }); }); @@ -241,83 +259,83 @@ describe('AppComponent', () => { beforeEach(() => { configurationService.get.mockReturnValue(of(createStateResource(configurationResource))); - component.navigateByConfiguration = jest.fn(); + component._navigateByConfiguration = jest.fn(); }); it('should call configuration service to get resource', () => { - component.evaluateNavigationByConfiguration(); + component._evaluateNavigationByConfiguration(); expect(configurationService.get).toHaveBeenCalled(); }); it('should call navigate by configuration', () => { - component.evaluateNavigationByConfiguration(); + component._evaluateNavigationByConfiguration(); - expect(component.navigateByConfiguration).toHaveBeenCalledWith(configurationResource); + expect(component._navigateByConfiguration).toHaveBeenCalledWith(configurationResource); }); it('should NOT call navigate by configuration if resource is loading', () => { configurationService.get.mockReturnValue(of(createEmptyStateResource<ConfigurationResource>(true))); - component.evaluateNavigationByConfiguration(); + component._evaluateNavigationByConfiguration(); - expect(component.navigateByConfiguration).not.toHaveBeenCalled(); + expect(component._navigateByConfiguration).not.toHaveBeenCalled(); }); }); describe('navigate by configuration', () => { beforeEach(() => { - component.unsubscribe = jest.fn(); + component._unsubscribe = jest.fn(); }); it('should navigate to postfach if settings link exists', () => { - component.navigateByConfiguration(createConfigurationResource([ConfigurationLinkRel.SETTING])); + component._navigateByConfiguration(createConfigurationResource([ConfigurationLinkRel.SETTING])); expect(router.navigate).toHaveBeenCalledWith(['/postfach']); }); it('should navigate to statistik if aggregation mapping link exists', () => { - component.navigateByConfiguration(createConfigurationResource([ConfigurationLinkRel.AGGREGATION_MAPPINGS])); + component._navigateByConfiguration(createConfigurationResource([ConfigurationLinkRel.AGGREGATION_MAPPINGS])); expect(router.navigate).toHaveBeenCalledWith(['/statistik']); }); it('should navigate to unavailable page if no link exists', () => { - component.navigateByConfiguration(createConfigurationResource()); + component._navigateByConfiguration(createConfigurationResource()); expect(router.navigate).toHaveBeenCalledWith(['/unavailable']); }); it('should call unsubscribe', () => { - component.navigateByConfiguration(createConfigurationResource()); + component._navigateByConfiguration(createConfigurationResource()); - expect(component.unsubscribe).toHaveBeenCalled(); + expect(component._unsubscribe).toHaveBeenCalled(); }); }); describe('navigate by api root', () => { beforeEach(() => { - component.unsubscribe = jest.fn(); + component._unsubscribe = jest.fn(); }); it('should navigate to benutzer und rollen if users link exists', () => { const apiRootResource: ApiRootResource = createApiRootResource([ApiRootLinkRel.USERS]); - component.navigateByApiRoot(apiRootResource); + component._navigateByApiRoot(apiRootResource); expect(router.navigate).toHaveBeenCalledWith([`/${ROUTES.BENUTZER}`]); }); it('should navigate to unavailable page if no link exists', () => { - component.navigateByApiRoot(createApiRootResource()); + component._navigateByApiRoot(createApiRootResource()); expect(router.navigate).toHaveBeenCalledWith([`/${ROUTES.UNAVAILABLE}`]); }); it('should call unsubscribe', () => { - component.navigateByApiRoot(createApiRootResource()); + component._navigateByApiRoot(createApiRootResource()); - expect(component.unsubscribe).toHaveBeenCalled(); + expect(component._unsubscribe).toHaveBeenCalled(); }); }); @@ -327,7 +345,7 @@ describe('AppComponent', () => { component.apiRootSubscription = <any>mock(Subscription); component.apiRootSubscription.unsubscribe = jest.fn(); - component.unsubscribe(); + component._unsubscribe(); expect(component.apiRootSubscription.unsubscribe).toHaveBeenCalled(); }); @@ -336,7 +354,7 @@ describe('AppComponent', () => { component.apiRootSubscription = undefined; const unsubscribeSpy: jest.SpyInstance = jest.spyOn(Subscription.prototype, 'unsubscribe'); - component.unsubscribe(); + component._unsubscribe(); expect(unsubscribeSpy).not.toHaveBeenCalled(); unsubscribeSpy.mockRestore(); @@ -348,7 +366,7 @@ describe('AppComponent', () => { component.configurationSubscription = <any>mock(Subscription); component.configurationSubscription.unsubscribe = jest.fn(); - component.unsubscribe(); + component._unsubscribe(); expect(component.configurationSubscription.unsubscribe).toHaveBeenCalled(); }); @@ -357,7 +375,7 @@ describe('AppComponent', () => { component.apiRootSubscription = undefined; const unsubscribeSpy: jest.SpyInstance = jest.spyOn(Subscription.prototype, 'unsubscribe'); - component.unsubscribe(); + component._unsubscribe(); expect(unsubscribeSpy).not.toHaveBeenCalled(); unsubscribeSpy.mockRestore(); diff --git a/alfa-client/apps/admin/src/app/app.component.ts b/alfa-client/apps/admin/src/app/app.component.ts index 6de5c0ab4da5fed7ffc763b86cdb18e253c44528..909b6cd198bd3ccb91dd61e922abb6a1ed79210c 100644 --- a/alfa-client/apps/admin/src/app/app.component.ts +++ b/alfa-client/apps/admin/src/app/app.component.ts @@ -33,15 +33,9 @@ import { Component, inject, OnInit } from '@angular/core'; import { Router, RouterLink, RouterOutlet } from '@angular/router'; import { AuthenticationService } from '@authentication'; import { hasLink } from '@ngxp/rest'; -import { - AdminLogoIconComponent, - NavbarComponent, - NavItemComponent, - OrgaUnitIconComponent, - UsersIconComponent, -} from '@ods/system'; +import { AdminLogoIconComponent, NavbarComponent, NavItemComponent, OrgaUnitIconComponent, UsersIconComponent, } from '@ods/system'; import { filter, Observable, Subscription } from 'rxjs'; -import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component'; +import { UserProfileButtonContainerComponent } from '../../../../libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component'; import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component'; @Component({ @@ -83,37 +77,49 @@ export class AppComponent implements OnInit { public readonly routes = ROUTES; ngOnInit(): void { - this.authenticationService.login().then(() => this.doAfterLoggedIn()); + this.authenticationService.login().then(() => this._doAfterLoggedIn()); } - doAfterLoggedIn(): void { + _doAfterLoggedIn(): void { this.apiRootStateResource$ = this.apiRootService.getApiRoot(); this.keycloakTokenService.registerAccessTokenProvider(); - this.evaluateInitialNavigation(); + this._evaluateInitialNavigation(); } - evaluateInitialNavigation(): void { + _evaluateInitialNavigation(): void { + if (this.router.url.startsWith('/?state')) { + this._subscribeApiRootForEvaluation(); + } else { + this.refreshForGuardEvaluation(); + } + } + + _subscribeApiRootForEvaluation(): void { this.apiRootSubscription = this.apiRootStateResource$ .pipe(filter(isLoaded), mapToResource<ApiRootResource>()) - .subscribe((apiRootResource: ApiRootResource) => this.evaluateNavigationByApiRoot(apiRootResource)); + .subscribe((apiRootResource: ApiRootResource) => this._evaluateNavigationByApiRoot(apiRootResource)); + } + + private refreshForGuardEvaluation(): void { + this.router.navigate([window.location.pathname]); } - evaluateNavigationByApiRoot(apiRootResource: ApiRootResource): void { + _evaluateNavigationByApiRoot(apiRootResource: ApiRootResource): void { if (hasLink(apiRootResource, ApiRootLinkRel.CONFIGURATION)) { - this.evaluateNavigationByConfiguration(); + this._evaluateNavigationByConfiguration(); } else { - this.navigateByApiRoot(apiRootResource); + this._navigateByApiRoot(apiRootResource); } } - evaluateNavigationByConfiguration(): void { + _evaluateNavigationByConfiguration(): void { this.configurationSubscription = this.configurationService .get() .pipe(filter(isLoaded), mapToResource<ApiRootResource>()) - .subscribe((configurationResource: ConfigurationResource) => this.navigateByConfiguration(configurationResource)); + .subscribe((configurationResource: ConfigurationResource) => this._navigateByConfiguration(configurationResource)); } - navigateByConfiguration(configurationResource: ConfigurationResource): void { + _navigateByConfiguration(configurationResource: ConfigurationResource): void { if (hasLink(configurationResource, ConfigurationLinkRel.SETTING)) { this.navigate(ROUTES.POSTFACH); } else if (hasLink(configurationResource, ConfigurationLinkRel.AGGREGATION_MAPPINGS)) { @@ -121,23 +127,23 @@ export class AppComponent implements OnInit { } else { this.navigate(ROUTES.UNAVAILABLE); } - this.unsubscribe(); + this._unsubscribe(); } - navigateByApiRoot(apiRootResource: ApiRootResource): void { + _navigateByApiRoot(apiRootResource: ApiRootResource): void { if (hasLink(apiRootResource, ApiRootLinkRel.USERS)) { this.navigate(ROUTES.BENUTZER); } else { this.navigate(ROUTES.UNAVAILABLE); } - this.unsubscribe(); + this._unsubscribe(); } private navigate(routePath: string): void { this.router.navigate(['/' + routePath]); } - unsubscribe(): void { + _unsubscribe(): void { if (isNotUndefined(this.apiRootSubscription)) this.apiRootSubscription.unsubscribe(); if (isNotUndefined(this.configurationSubscription)) this.configurationSubscription.unsubscribe(); } diff --git a/alfa-client/apps/admin/src/app/app.guard.spec.ts b/alfa-client/apps/admin/src/app/app.guard.spec.ts index 24e0a21eca4fb630c66112e19ba4b0024bf983a9..41cfe7506366e35bdd3d16d4f24d054c16eef40c 100644 --- a/alfa-client/apps/admin/src/app/app.guard.spec.ts +++ b/alfa-client/apps/admin/src/app/app.guard.spec.ts @@ -27,6 +27,7 @@ import { createStateResource, LinkRelationName, StateResource } from '@alfa-clie import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { TestBed } from '@angular/core/testing'; import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router'; +import { AuthenticationService } from '@authentication'; import { Resource } from '@ngxp/rest'; import { createConfigurationResource } from 'libs/admin/configuration-shared/test/configuration'; import { createApiRootResource } from 'libs/api-root-shared/test/api-root'; @@ -139,10 +140,49 @@ describe('Guard', () => { const resource: Resource = createDummyResource([DummyLinkRel.DUMMY]); const stateResource$: Observable<StateResource<Resource>> = of(createStateResource(resource)); + let authService: Mock<AuthenticationService>; + let router: Mock<Router>; + + beforeEach(() => { + authService = { + ...mock(AuthenticationService), + isLoggedIn: jest.fn().mockReturnValue(true), + }; + router = mock(Router); + + TestBed.configureTestingModule({ + providers: [ + { + provide: Router, + useValue: router, + }, + { + provide: AuthenticationService, + useValue: authService, + }, + ], + }); + }); + afterEach(() => { jest.restoreAllMocks(); }); + it('should call authenticationService isLoggedIn', () => { + evaluate().subscribe(); + + expect(authService.isLoggedIn).toHaveBeenCalled(); + }); + + it('should return observable true if not logged in', (done) => { + authService.isLoggedIn.mockReturnValue(false); + + evaluate().subscribe((resolvedValue) => { + expect(resolvedValue).toEqual(true); + done(); + }); + }); + it('should evaluate resource', () => { const urlTreeMock: Mock<UrlTree> = mock(UrlTree); const evaluateResourceSpy: jest.SpyInstance = jest @@ -151,7 +191,7 @@ describe('Guard', () => { evaluate().subscribe(); - expect(evaluateResourceSpy).toHaveBeenCalledWith(resource, DummyLinkRel.DUMMY); + expect(evaluateResourceSpy).toHaveBeenCalledWith(resource, DummyLinkRel.DUMMY, router); }); it('should return evaluated boolean', (done) => { @@ -181,34 +221,23 @@ describe('Guard', () => { }); describe('evaluate resource', () => { - let router: Mock<Router>; - - beforeEach(() => { - router = mock(Router); - - TestBed.configureTestingModule({ - providers: [ - { - provide: Router, - useValue: router, - }, - ], - }); - }); + const router: Mock<Router> = mock(Router); afterEach(() => { jest.restoreAllMocks(); }); it('should return true if link exists', () => { - const result: boolean = <boolean>evaluateResource(createDummyResource([DummyLinkRel.DUMMY])); + const result: boolean = <boolean>( + Guard.evaluateResource(createDummyResource([DummyLinkRel.DUMMY]), DummyLinkRel.DUMMY, useFromMock(router)) + ); expect(result).toBeTruthy(); }); describe('if link not exists', () => { it('should call router', () => { - evaluateResource(createDummyResource()); + Guard.evaluateResource(createDummyResource(), DummyLinkRel.DUMMY, useFromMock(router)); expect(router.createUrlTree).toHaveBeenCalledWith(['/unavailable']); }); @@ -217,14 +246,10 @@ describe('Guard', () => { const urlTree: Mock<UrlTree> = mock(UrlTree); router.createUrlTree.mockReturnValue(urlTree); - const result: UrlTree = <UrlTree>evaluateResource(createDummyResource()); + const result: UrlTree = <UrlTree>Guard.evaluateResource(createDummyResource(), DummyLinkRel.DUMMY, useFromMock(router)); expect(result).toEqual(urlTree); }); }); - - function evaluateResource(dummyResource: Resource): boolean | UrlTree { - return <boolean | UrlTree>TestBed.runInInjectionContext(() => Guard.evaluateResource(dummyResource, DummyLinkRel.DUMMY)); - } }); }); diff --git a/alfa-client/apps/admin/src/app/app.guard.ts b/alfa-client/apps/admin/src/app/app.guard.ts index 85625be095c256aa593faa2d0d415352258aa1e0..c61df188c0c4ea38de5c66cfe69a55e39fc780ce 100644 --- a/alfa-client/apps/admin/src/app/app.guard.ts +++ b/alfa-client/apps/admin/src/app/app.guard.ts @@ -21,24 +21,25 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { ConfigurationService } from '@admin-client/configuration-shared'; import { ROUTES } from '@admin-client/shared'; import { ApiRootService } from '@alfa-client/api-root-shared'; import { LinkRelationName, mapToResource, StateResource } from '@alfa-client/tech-shared'; import { inject } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivateFn, Router, UrlTree } from '@angular/router'; +import { AuthenticationService } from '@authentication'; import { hasLink, Resource } from '@ngxp/rest'; -import { filter, map, Observable } from 'rxjs'; +import { filter, map, Observable, of } from 'rxjs'; import { GuardData } from './app.routes'; -import { ConfigurationService } from '@admin-client/configuration-shared'; import * as Guard from './app.guard'; -export const apiRootGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => { +export const apiRootGuard: CanActivateFn = (route: ActivatedRouteSnapshot): Observable<true | UrlTree> => { const apiRootService: ApiRootService = inject(ApiRootService); return Guard.evaluate(apiRootService.getApiRoot(), getLinkRelationName(route)); }; -export const configurationGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => { +export const configurationGuard: CanActivateFn = (route: ActivatedRouteSnapshot): Observable<true | UrlTree> => { const configurationService: ConfigurationService = inject(ConfigurationService); return Guard.evaluate(configurationService.get(), getLinkRelationName(route)); }; @@ -46,11 +47,16 @@ export const configurationGuard: CanActivateFn = (route: ActivatedRouteSnapshot) export function evaluate( stateResource$: Observable<StateResource<Resource>>, linkRelationName: LinkRelationName, -): Observable<boolean | UrlTree> { +): Observable<true | UrlTree> { + const authService = inject(AuthenticationService); + const router = inject(Router); + + if (!authService.isLoggedIn()) return of(true); + return stateResource$.pipe( - filter((stateResource: StateResource<Resource>) => stateResource.loaded), + filter((stateResource: StateResource<Resource>): boolean => stateResource.loaded), mapToResource<Resource>(), - map((resource: Resource) => Guard.evaluateResource(resource, linkRelationName)), + map((resource: Resource): true | UrlTree => Guard.evaluateResource(resource, linkRelationName, router)), ); } @@ -58,10 +64,10 @@ function getLinkRelationName(route: ActivatedRouteSnapshot): string { return (<GuardData>route.data).linkRelName; } -export function evaluateResource(resource: Resource, linkRelationName: LinkRelationName): boolean | UrlTree { - return hasLink(resource, linkRelationName) ? true : redirectToUnavailable(); +export function evaluateResource(resource: Resource, linkRelationName: LinkRelationName, router: Router): true | UrlTree { + return hasLink(resource, linkRelationName) ? true : redirectToUnavailable(router); } -function redirectToUnavailable(): UrlTree { - return inject(Router).createUrlTree(['/' + ROUTES.UNAVAILABLE]); +function redirectToUnavailable(router: Router): UrlTree { + return router.createUrlTree(['/' + ROUTES.UNAVAILABLE]); } diff --git a/alfa-client/apps/admin/src/app/app.routes.ts b/alfa-client/apps/admin/src/app/app.routes.ts index 279141277b1bbd700ca663d7009e886b89453429..c5974993c237ec15254220a64157b63ae0f46136 100644 --- a/alfa-client/apps/admin/src/app/app.routes.ts +++ b/alfa-client/apps/admin/src/app/app.routes.ts @@ -44,6 +44,7 @@ export const appRoutes: Route[] = [ path: ROUTES.POSTFACH, component: PostfachPageComponent, title: 'Admin | Postfach', + runGuardsAndResolvers: 'always', canActivate: [configurationGuard], data: <GuardData>{ linkRelName: ConfigurationLinkRel.SETTING }, }, @@ -51,6 +52,7 @@ export const appRoutes: Route[] = [ path: ROUTES.BENUTZER, component: UserListPageComponent, title: 'Admin | Benutzer', + runGuardsAndResolvers: 'always', canActivate: [apiRootGuard], data: <GuardData>{ linkRelName: ApiRootLinkRel.USERS }, }, @@ -58,6 +60,7 @@ export const appRoutes: Route[] = [ path: ROUTES.BENUTZER_NEU, component: UserFormPageComponent, title: 'Admin | Benutzer anlegen', + runGuardsAndResolvers: 'always', canActivate: [apiRootGuard], data: <GuardData>{ linkRelName: ApiRootLinkRel.USERS }, }, @@ -65,6 +68,7 @@ export const appRoutes: Route[] = [ path: ROUTES.BENUTZER_ID, component: UserFormComponent, title: 'Admin | Benutzer bearbeiten', + runGuardsAndResolvers: 'always', canActivate: [apiRootGuard], data: <GuardData>{ linkRelName: ApiRootLinkRel.USERS }, }, @@ -82,6 +86,7 @@ export const appRoutes: Route[] = [ path: ROUTES.STATISTIK, component: StatistikPageComponent, title: 'Admin | Statistik', + runGuardsAndResolvers: 'always', canActivate: [configurationGuard], data: <GuardData>{ linkRelName: ConfigurationLinkRel.AGGREGATION_MAPPINGS }, }, @@ -89,7 +94,13 @@ export const appRoutes: Route[] = [ path: ROUTES.STATISTIK_NEU, component: StatistikFieldsFormPageComponent, title: 'Admin | Statistik weitere Felder auswerten', + runGuardsAndResolvers: 'always', canActivate: [configurationGuard], data: <GuardData>{ linkRelName: ConfigurationLinkRel.AGGREGATION_MAPPINGS }, }, + { + path: '**', + component: UnavailablePageComponent, + title: 'Unavailable', + }, ]; diff --git a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.spec.ts b/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.spec.ts deleted file mode 100644 index b40a661442d6728f6c7819a0548e556d615197a8..0000000000000000000000000000000000000000 --- a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { dispatchEventFromFixture, getElementFromFixture, mock, Mock } from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { DropdownMenuButtonItemComponent, DropdownMenuComponent, LogoutIconComponent } from '@ods/system'; -import { AuthenticationService } from '@authentication'; -import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { MockComponent } from 'ng-mocks'; -import { UserProfileButtonContainerComponent } from './user-profile.button-container.component'; - -describe('UserProfileButtonContainerComponent', () => { - let component: UserProfileButtonContainerComponent; - let fixture: ComponentFixture<UserProfileButtonContainerComponent>; - - const authenticationService: Mock<AuthenticationService> = mock(AuthenticationService); - - const popupButtonContent: string = getDataTestIdOf('popup-button-content'); - const popupLogoutButton: string = getDataTestIdOf('popup-logout-button'); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [UserProfileButtonContainerComponent], - imports: [ - RouterTestingModule, - MockComponent(DropdownMenuComponent), - MockComponent(DropdownMenuButtonItemComponent), - MockComponent(LogoutIconComponent), - ], - providers: [ - { - provide: AuthenticationService, - useValue: authenticationService, - }, - ], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(UserProfileButtonContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('ngOnInit', () => { - it('should call authService to get current user initials', () => { - component.ngOnInit(); - - expect(authenticationService.getCurrentUserInitials).toHaveBeenCalled(); - }); - }); - - describe('popup button', () => { - it('should show initials', () => { - component.currentUserInitials = 'AV'; - fixture.detectChanges(); - - const popupButtonContentElement: HTMLElement = getElementFromFixture(fixture, popupButtonContent); - - expect(popupButtonContentElement.textContent.trim()).toEqual('AV'); - }); - }); - - describe('logout', () => { - it('should call authService logout', () => { - dispatchEventFromFixture(fixture, popupLogoutButton, 'itemClicked'); - - expect(authenticationService.logout).toHaveBeenCalled(); - }); - }); -}); diff --git a/alfa-client/apps/alfa-e2e/src/components/attachment/attachment.e2e.component.ts b/alfa-client/apps/alfa-e2e/src/components/attachment/attachment.e2e.component.ts index fe12579e28ed202ae4b72b4f54003a2ae149cd75..dbcb98b4dc2a1a77eb110910dd711bf5b9e40dff 100644 --- a/alfa-client/apps/alfa-e2e/src/components/attachment/attachment.e2e.component.ts +++ b/alfa-client/apps/alfa-e2e/src/components/attachment/attachment.e2e.component.ts @@ -24,42 +24,45 @@ import { convertToDataTestId } from '../../support/tech.util'; export class AttachmentContainerE2EComponent { - private readonly locatorFileUploadInput: string = '-file-upload-input'; + private readonly multiUploadInput: string = 'multi-file-upload-button'; + private readonly attachmentList: AttachmentListE2EComponent = new AttachmentListE2EComponent(); public getList(): AttachmentListE2EComponent { return this.attachmentList; } - public getUploadInput() { - return cy.getTestElement(this.locatorFileUploadInput); + //TODO Rename getMultiUploadButton + public getUploadInput(): Cypress.Chainable<HTMLElement> { + return cy.getTestElement(this.multiUploadInput); } } export class AttachmentListE2EComponent { private readonly locatorRoot: string = 'file-list'; - private readonly downloadAttachmentsButton: string = 'download-archive-file-button'; - public getRoot() { + private readonly downloadArchiveButton: string = 'download-archive-file-button'; + + public getRoot(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.locatorRoot); } - public getItem(fileName: string): AttachmentE2EItem { - return new AttachmentE2EItem(fileName); + public getItem(fileName: string): AttachmentListItemE2EComponent { + return new AttachmentListItemE2EComponent(fileName); } - public getDownloadAttachmentsButton(): Cypress.Chainable<JQuery<HTMLElement>> { - return this.getRoot().findTestElementWithClass(this.downloadAttachmentsButton); + public getLoadingOrErrorItem(fileName: string): LoadingErrorAttachmentListeItemE2EComponent { + return new LoadingErrorAttachmentListeItemE2EComponent(fileName); } - public downloadAttachments(): Cypress.Chainable<any> { - return this.getDownloadAttachmentsButton().click(); + public getDownloadArchiveButton(): Cypress.Chainable<HTMLElement> { + return this.getRoot().getTestElement(this.downloadArchiveButton); } } -class AttachmentE2EItem { - private readonly locatorDeleteButton: string = 'delete-file-button'; - private readonly locatorDownloadButton: string = 'download-file-button'; +export class AttachmentListItemE2EComponent { + private readonly deleteButton: string = 'delete-file-button'; + private readonly downloadButton: string = 'download-file-button'; private locatorRoot: string; @@ -67,15 +70,28 @@ class AttachmentE2EItem { this.locatorRoot = convertToDataTestId(this.fileName) + '-file-item'; } - public getRoot() { + public getRoot(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.locatorRoot); } - public getDeleteButton() { - return this.getRoot().findTestElementWithClass(this.locatorDeleteButton); + public getDeleteButton(): Cypress.Chainable<HTMLElement> { + return this.getRoot().findTestElementWithClass(this.deleteButton); + } + + public getDownloadButton(): Cypress.Chainable<HTMLElement> { + return this.getRoot().findTestElementWithClass(this.downloadButton); + } +} + +export class LoadingErrorAttachmentListeItemE2EComponent { + private readonly root: string; + private readonly attachmentSuffix: string = '-file-upload-list-item-attachment-upload'; + + constructor(private fileName: string) { + this.root = convertToDataTestId(this.fileName) + this.attachmentSuffix; } - public getDownloadButton() { - return this.getRoot().findTestElementWithClass(this.locatorDownloadButton); + public getRoot(): Cypress.Chainable<HTMLElement> { + return cy.getTestElement(this.root); } } diff --git a/alfa-client/apps/alfa-e2e/src/components/kommentar/kommentar-list.e2e.component.ts b/alfa-client/apps/alfa-e2e/src/components/kommentar/kommentar-list.e2e.component.ts index 42e791b9899b0348637184324cfcf91f0ef5d60b..09cebe8b80f722d1869036d55767e47104031728 100644 --- a/alfa-client/apps/alfa-e2e/src/components/kommentar/kommentar-list.e2e.component.ts +++ b/alfa-client/apps/alfa-e2e/src/components/kommentar/kommentar-list.e2e.component.ts @@ -25,54 +25,60 @@ import { convertToDataTestId } from '../../support/tech.util'; import { AttachmentContainerE2EComponent } from '../attachment/attachment.e2e.component'; import { UserProfileE2EComponent } from '../user-profile/user-profile.component.e2e'; -export class KommentareInVorgangE2EComponent { - readonly locatorHinzufuegenButton: string = 'create-kommentar'; - readonly locatorFormular: string = 'kommentar-formular'; - readonly locatorFormularSpeichernButton: string = 'add-button'; - readonly locatorFormularAbbrechenButton: string = 'cancel-button'; - readonly locatorKommentarList: string = 'kommentar-list-in-vorgang'; - readonly locatorTextInput: string = 'Kommentar-textarea-input'; - readonly locatorTextError: string = 'Kommentar-textarea-error'; - - private readonly attachmentContainer: AttachmentContainerE2EComponent = - new AttachmentContainerE2EComponent(); - - readonly locatorRoot: string = 'kommentar-container-in-vorgang'; - - public getRoot() { - return cy.getTestElement(this.locatorRoot); +export class KommentarListInVorgangE2EComponent { + readonly hinzufuegenButton: string = 'create-kommentar'; + + readonly root: string = 'kommentar-container-in-vorgang'; + + readonly kommentarList: string = 'kommentar-list-in-vorgang'; + + readonly formular: string = 'kommentar-formular'; + readonly formularItem: string = 'kommentar-list-item-form'; + readonly formularSpeichernButton: string = 'add-button'; + readonly formularAbbrechenButton: string = 'cancel-button'; + readonly textInput: string = 'Kommentar-textarea-input'; + readonly textError: string = 'Kommentar-textarea-error'; + + private readonly attachmentContainer: AttachmentContainerE2EComponent = new AttachmentContainerE2EComponent(); + + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); + } + + public getFormular(): Cypress.Chainable<Element> { + return cy.getTestElement(this.formular); } - public getFormular() { - return cy.getTestElement(this.locatorFormular); + public getFormulare(): Cypress.Chainable<Element[]> { + return cy.getTestElement(this.formularItem); } - public getHinzufuegenButton() { - return cy.getTestElement(this.locatorHinzufuegenButton); + public getHinzufuegenButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.hinzufuegenButton); } - public getFormularSpeichernButton() { - return cy.getTestElement(this.locatorFormularSpeichernButton); + public getFormularSpeichernButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.formularSpeichernButton); } - public getFormularAbbrechenButton() { - return cy.getTestElement(this.locatorFormularAbbrechenButton); + public getFormularAbbrechenButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.formularAbbrechenButton); } - public getKommentarList() { - return cy.getTestElement(this.locatorKommentarList); + public getKommentarList(): Cypress.Chainable<Element> { + return cy.getTestElement(this.kommentarList); } - public getTextInput() { - return cy.getTestElement(this.locatorTextInput); + public getTextInput(): Cypress.Chainable<Element> { + return cy.getTestElement(this.textInput); } - public getTextError() { - return cy.getTestElement(this.locatorTextError); + public getTextError(): Cypress.Chainable<Element> { + return cy.getTestElement(this.textError); } - public getKommentar(text: string): KommentarInVorgangE2E { - return new KommentarInVorgangE2E(text); + public getKommentar(text: string): KommentarInVorgangE2EComponent { + return new KommentarInVorgangE2EComponent(text); } public getAttachmentContainer(): AttachmentContainerE2EComponent { @@ -80,26 +86,24 @@ export class KommentareInVorgangE2EComponent { } } -export class KommentarInVorgangE2E { - //TODO: Rename to KommentarInVorgangE2EComponent - - private readonly locatorCreatedAt: string = 'kommentar-created-at'; +export class KommentarInVorgangE2EComponent { + private readonly root: string; - private readonly locatorRoot: string; + private readonly createdAtInput: string = 'kommentar-created-at'; constructor(private text: string) { - this.locatorRoot = 'kommentar-item-' + convertToDataTestId(this.text); + this.root = 'kommentar-item-' + convertToDataTestId(this.text); } - public getRoot() { - return cy.getTestElement(this.locatorRoot); + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); } public getUserProfile(): UserProfileE2EComponent { - return new UserProfileE2EComponent(this.locatorRoot); + return new UserProfileE2EComponent(this.root); } - public getCreatedAt() { - return cy.getTestElement(this.locatorCreatedAt); + public getCreatedAt(): Cypress.Chainable<Element> { + return this.getRoot().findTestElementWithClass(this.createdAtInput); } } diff --git a/alfa-client/apps/alfa-e2e/src/components/postfach/postfach-mail-formular.e2e.component.ts b/alfa-client/apps/alfa-e2e/src/components/postfach/postfach-mail-formular.e2e.component.ts index fc4edd22fe768eb8be089b6142314fc6d0227e1c..c1348e724905b5cb8bc0daa0b8a02521cd9b23d9 100644 --- a/alfa-client/apps/alfa-e2e/src/components/postfach/postfach-mail-formular.e2e.component.ts +++ b/alfa-client/apps/alfa-e2e/src/components/postfach/postfach-mail-formular.e2e.component.ts @@ -25,43 +25,42 @@ import { getTestElement } from '../../support/cypress-helper'; import { AttachmentContainerE2EComponent } from '../attachment/attachment.e2e.component'; export class PostfachMailFormularE2EComponent { - private readonly locatorEmpfaenger: string = 'postfach-empfaenger'; - private readonly locatorBetreff: string = 'Betreff-text-input'; - private readonly locatorBetreffError: string = 'Betreff-text-error'; - private readonly locatorText: string = 'Text-textarea-input'; - private readonly locatorTextError: string = 'Text-textarea-error'; - private readonly locatorSendButton: string = 'postfach-send-button'; + private readonly empfaenger: string = 'postfach-empfaenger'; + private readonly betreffInput: string = 'Betreff-text-input'; + private readonly betreffError: string = 'Betreff-text-error'; + private readonly textInput: string = 'Text-textarea-input'; + private readonly textError: string = 'Text-textarea-error'; + private readonly replyOptionCheckbox: string = 'postfach-reply-option'; + private readonly sendButton: string = 'postfach-send-button'; - private readonly replyOption: string = 'postfach-reply-option'; - private readonly attachmentContainer: AttachmentContainerE2EComponent = - new AttachmentContainerE2EComponent(); + private readonly attachmentContainer: AttachmentContainerE2EComponent = new AttachmentContainerE2EComponent(); - public getEmpfaenger() { - return cy.getTestElement(this.locatorEmpfaenger); + public getEmpfaenger(): Cypress.Chainable<HTMLElement> { + return cy.getTestElement(this.empfaenger); } - public getBetreff() { - return cy.getTestElement(this.locatorBetreff); + public getBetreff(): Cypress.Chainable<HTMLElement> { + return cy.getTestElement(this.betreffInput); } - public getBetreffError() { - return cy.getTestElement(this.locatorBetreffError); + public getBetreffError(): Cypress.Chainable<HTMLElement> { + return cy.getTestElement(this.betreffError); } - public getText() { - return cy.getTestElement(this.locatorText); + public getText(): Cypress.Chainable<HTMLElement> { + return cy.getTestElement(this.textInput); } - public getTextError() { - return cy.getTestElement(this.locatorTextError); + public getTextError(): Cypress.Chainable<HTMLElement> { + return cy.getTestElement(this.textError); } - public getSendButton() { - return cy.getTestElement(this.locatorSendButton); + public getSendButton(): Cypress.Chainable<HTMLElement> { + return cy.getTestElement(this.sendButton); } - public getReplyOption() { - return getTestElement(this.replyOption).find('.mdc-checkbox__native-control'); + public getReplyOption(): Cypress.Chainable<HTMLElement> { + return getTestElement(this.replyOptionCheckbox).find('.mdc-checkbox__native-control'); } public getAttachmentContainer(): AttachmentContainerE2EComponent { diff --git a/alfa-client/apps/alfa-e2e/src/components/user-assistance/help-menu.component.e2e.ts b/alfa-client/apps/alfa-e2e/src/components/user-assistance/help-menu.component.e2e.ts index 6900b4f81bba5213341add0864bec162856037fb..9bdd8978dbaafbf31aec2301c0a66348be778a6d 100644 --- a/alfa-client/apps/alfa-e2e/src/components/user-assistance/help-menu.component.e2e.ts +++ b/alfa-client/apps/alfa-e2e/src/components/user-assistance/help-menu.component.e2e.ts @@ -1,4 +1,4 @@ -import { shouldHaveAttribute } from "../../support/cypress.util"; +import { shouldHaveAttribute } from '../../support/cypress.util'; /* * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den @@ -26,8 +26,8 @@ import { shouldHaveAttribute } from "../../support/cypress.util"; export class HelpMenuE2EComponent { private readonly root: string = 'help-menu'; private readonly button: string = 'help-menu-button'; - private readonly dropdownButton: string ='dropdown-button'; - private readonly openDocumentationButton: string = 'open-documentation-button'; + private readonly dropdownButton: string = 'dropdown-button'; + private readonly documentation: string = 'documentation'; private readonly openImpressumButton: string = 'impressum'; public getRoot() { @@ -42,15 +42,15 @@ export class HelpMenuE2EComponent { return this.getRoot().getTestElement(this.dropdownButton); } - public getOpenDocumentationButton() { - return this.getRoot().getTestElementWithOid(this.openDocumentationButton); + public getDocumentation() { + return this.getRoot().getTestElementWithOid(this.documentation); } public getImpressumButton(): Cypress.Chainable<Element> { - return cy.getTestElement(this.openImpressumButton) + return cy.getTestElement(this.openImpressumButton); } public impressumLinkIs(link: string): void { - shouldHaveAttribute(this.getImpressumButton().find('a'),'href', link) + shouldHaveAttribute(this.getImpressumButton().find('a'), 'href', link); } } diff --git a/alfa-client/apps/alfa-e2e/src/components/vorgang/vorgang-bescheid-wizard.e2e.component.ts b/alfa-client/apps/alfa-e2e/src/components/vorgang/vorgang-bescheid-wizard.e2e.component.ts index 057b39b29c7de8819c69e745af44cda24e300859..4b1e36d8940baf9c31b74c0db1496400ff3f3a16 100644 --- a/alfa-client/apps/alfa-e2e/src/components/vorgang/vorgang-bescheid-wizard.e2e.component.ts +++ b/alfa-client/apps/alfa-e2e/src/components/vorgang/vorgang-bescheid-wizard.e2e.component.ts @@ -49,7 +49,6 @@ export class VorgangBescheidWizardE2EComponent { private readonly uploadBescheidFileButton: string = '-single-file-upload-button'; private readonly uploadAttachmentButton: string = 'Anhang_hochladen-file-upload-button'; private readonly uploadAutomaticBescheid: string = 'create-bescheid-document-button'; - private readonly mailTextArea: string = 'Text-textarea-editor'; private readonly saveBescheid: string = 'save-button'; private readonly sendBescheid: string = 'send-button'; private readonly confirmAndSaveButton: string = 'confirm-and-save-button'; @@ -63,10 +62,6 @@ export class VorgangBescheidWizardE2EComponent { private readonly bescheidDocument: string = 'bescheid-document'; private readonly attachmentDocument: string = 'bescheid-attachments'; - private readonly bescheidUploadSpinner: string = '[data-test-id="bescheid-document"] ods-spinner-icon'; - private readonly attachmentUploadSpinner: string = '[data-test-id="bescheid-attachments"] ods-spinner-icon'; - private readonly bescheidSaveSpinner: string = '[data-test-id="confirm-and-save-button"] ods-spinner-icon'; - private readonly sendenSpinner: string = '[data-test-id="send-button"] ods-spinner-icon'; private readonly missingBescheidDocumentMessage: string = 'missing-bescheid-document-error-message'; private locatorRoot: string = 'bescheid-wizard'; @@ -142,35 +137,35 @@ export class VorgangBescheidWizardE2EComponent { return cy.getTestElement(this.closeDialog); } - public getCloseVerwerfenButton(): Cypress.Chainable<JQuery<HTMLElement>> { + public getCloseVerwerfenButton(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.bescheidVerwerfenButton); } - public getCloseSpeichernButton(): Cypress.Chainable<JQuery<HTMLElement>> { + public getCloseSpeichernButton(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.bescheidSpeichernButton); } - public getUploadBescheidButton(): Cypress.Chainable<JQuery<HTMLElement>> { + public getUploadBescheidButton(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.uploadBescheidFileButton); } - public getUploadAttachmentButton(): Cypress.Chainable<JQuery<HTMLElement>> { + public getUploadAttachmentButton(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.uploadAttachmentButton); } - public getFileBescheidValidInWizard(): Cypress.Chainable<JQuery<HTMLElement>> { + public getFileBescheidValidInWizard(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.locatorRoot).find(`[data-test-id=${this.fileBescheidValid}]`); } - public getFileAnhangValidInWizard(): Cypress.Chainable<JQuery<HTMLElement>> { + public getFileAnhangValidInWizard(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.locatorRoot).find(`[data-test-id=${this.fileAnhangValid}]`); } - public getAutomaticBescheidFileInWizard() { + public getAutomaticBescheidFileInWizard(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.locatorRoot).find(`[data-test-id=${this.fileAutomaticBescheid}]`); } - public getDeleteButtonOfElement(element: string): Cypress.Chainable<JQuery<HTMLElement>> { + public getDeleteButtonOfElement(element: string): Cypress.Chainable<HTMLElement> { return cy.getTestElement(element).find('[title="Anhang löschen"]'); } @@ -178,30 +173,14 @@ export class VorgangBescheidWizardE2EComponent { return filename.replace(/\./g, '') + '-file-item'; } - public getBescheidDocument(): Cypress.Chainable<JQuery<HTMLElement>> { + public getBescheidDocument(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.bescheidDocument); } - public getAttachmentDocument(): Cypress.Chainable<JQuery<HTMLElement>> { + public getAttachmentDocument(): Cypress.Chainable<HTMLElement> { return cy.getTestElement(this.attachmentDocument); } - public getBescheidUploadSpinner(): Cypress.Chainable<JQuery<HTMLElement>> { - return cy.get(this.bescheidUploadSpinner); - } - - public getAttachmentUploadSpinner(): Cypress.Chainable<JQuery<HTMLElement>> { - return cy.get(this.attachmentUploadSpinner); - } - - public getBescheidSaveSpinner(): Cypress.Chainable<JQuery<HTMLElement>> { - return cy.get(this.bescheidSaveSpinner); - } - - public getSendenSpinner(): Cypress.Chainable<JQuery<HTMLElement>> { - return cy.get(this.sendenSpinner); - } - public getMailText(): Cypress.Chainable<JQuery<HTMLElement>> { return cy.getTestElement(this.nachrichtText); } @@ -274,14 +253,6 @@ export class VorgangBescheidWizardE2EComponent { uploadFile(this.getUploadAttachmentButton(), fileName); } - public bescheidUploadSpinnerIsClosed(): void { - this.getBescheidUploadSpinner().should('not.exist'); - } - - public attachmentSpinnerIsClosed(): void { - this.getAttachmentUploadSpinner().should('not.exist'); - } - public enterDate(difference: number): void { this.getDateInput().should('exist'); enterWith(this.getDateInput(), getAdjustedDateGerman(difference)); diff --git a/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-in-vorgang.e2e.component.ts b/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-in-vorgang.e2e.component.ts index 36b36d076f83254d26bdaa6babef3975300ebedd..753fe1f790e2f4cdcfc140f93ae23681764b3736 100644 --- a/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-in-vorgang.e2e.component.ts +++ b/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-in-vorgang.e2e.component.ts @@ -26,38 +26,37 @@ import { AttachmentContainerE2EComponent } from '../attachment/attachment.e2e.co import { StatusE2EComponent } from './wiedervorlage-status.e2e.component'; export class WiedervorlageInVorgangE2EComponent { - private readonly locatorFrist: string = 'frist'; - private readonly locatorBetreff: string = 'betreff'; - private readonly locatorLink: string = 'link'; - private readonly locatorExpandButon: string = 'expand-button'; - private readonly locatorExpandable: string = 'expandable'; - private readonly attachmentContainer: AttachmentContainerE2EComponent = - new AttachmentContainerE2EComponent(); + private readonly frist: string = 'frist'; + private readonly betreff: string = 'betreff'; + private readonly link: string = 'link'; + private readonly expandButton: string = 'expand-button'; + private readonly expandable: string = 'expandable'; + private readonly attachmentContainer: AttachmentContainerE2EComponent = new AttachmentContainerE2EComponent(); private locatorRoot: string; - constructor(private betreff: string) { - this.locatorRoot = replaceAllWhitespaces(this.betreff, '_'); + constructor(private betreffIdentificator: string) { + this.locatorRoot = replaceAllWhitespaces(this.betreffIdentificator, '_'); } - public getRoot() { + public getRoot(): Cypress.Chainable<Element> { return cy.getTestElement(this.locatorRoot); } - public getLink() { - return this.getRoot().findTestElementWithClass(this.locatorLink); + public getLink(): Cypress.Chainable<Element> { + return this.getRoot().findTestElementWithClass(this.link); } - public getFrist() { - return this.getRoot().findTestElementWithClass(this.locatorFrist); + public getFrist(): Cypress.Chainable<Element> { + return this.getRoot().findTestElementWithClass(this.frist); } - public getBetreff() { - return this.getRoot().findTestElementWithClass(this.locatorBetreff); + public getBetreff(): Cypress.Chainable<Element> { + return this.getRoot().findTestElementWithClass(this.betreff); } - public getExpandButton() { - return this.getRoot().findTestElementWithClass(this.locatorExpandButon); + public getExpandButton(): Cypress.Chainable<Element> { + return this.getRoot().findTestElementWithClass(this.expandButton); } public getStatus(): StatusE2EComponent { @@ -68,8 +67,8 @@ export class WiedervorlageInVorgangE2EComponent { this.getExpandButton().click(); } - public getExpandable() { - return this.getRoot().findTestElementWithClass(this.locatorExpandable); + public getExpandable(): Cypress.Chainable<Element> { + return this.getRoot().findTestElementWithClass(this.expandable); } public getAttachmentContainer(): AttachmentContainerE2EComponent { diff --git a/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-page.e2e.component.ts b/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-page.e2e.component.ts index e17043afdfaa7c7de564ec7b244ff85b1761985a..a647445a85ed68f65dccd37e8f1416dd0c03c396 100644 --- a/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-page.e2e.component.ts +++ b/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-page.e2e.component.ts @@ -34,31 +34,31 @@ export class WiedervorlageE2EComponent { private readonly attachmentContainer: AttachmentContainerE2EComponent = new AttachmentContainerE2EComponent(); - public getBetreff() { + public getBetreff(): Cypress.Chainable<Element> { return cy.getTestElement(this.locatorBetreffInput); } - public getBetreffError() { + public getBetreffError(): Cypress.Chainable<Element> { return cy.getTestElement(this.locatorBetreffError); } - public getBeschreibung() { + public getBeschreibung(): Cypress.Chainable<Element> { return cy.getTestElement(this.locatorBeschreibungInput); } - public getFrist() { + public getFrist(): Cypress.Chainable<Element> { return cy.getTestElement(this.locatorFristInput); } - public getDatumError() { + public getDatumError(): Cypress.Chainable<Element> { return cy.getTestElement(this.locatorDatumError); } - public getStatusDot() { + public getStatusDot(): Cypress.Chainable<Element> { return cy.getTestElementWithClass(this.locatorStatusDot); } - public getSpeichernButton() { + public getSpeichernButton(): Cypress.Chainable<Element> { return cy.getTestElement(this.locatorSpeichernButton); } diff --git a/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlagen-in-vorgang.e2e.component.ts b/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlagen-in-vorgang.e2e.component.ts index 91718623842de6e64bb8c0a863ba2146606d68c4..0f5d46f7ab8f257fc75e3f6a1bcd9d3cfd80da56 100644 --- a/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlagen-in-vorgang.e2e.component.ts +++ b/alfa-client/apps/alfa-e2e/src/components/wiedervorlage/wiedervorlagen-in-vorgang.e2e.component.ts @@ -24,18 +24,18 @@ import { WiedervorlageInVorgangE2EComponent } from './wiedervorlage-in-vorgang.e2e.component'; export class WiedervorlagenInVorgangE2EComponent { - private readonly locatorCreateWiedervorlageButton: string = 'create-wiedervorlage'; - private readonly locatorRoot: string = 'wiedervorlagen-in-vorgang'; + private readonly createWiedervorlageButton: string = 'create-wiedervorlage'; + private readonly root: string = 'wiedervorlagen-in-vorgang'; - public getRoot() { - return cy.getTestElement(this.locatorRoot); + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); } public getWiedervorlage(betreff: string): WiedervorlageInVorgangE2EComponent { return new WiedervorlageInVorgangE2EComponent(betreff); } - public getCreateWiedervorlageButton() { - return cy.getTestElement(this.locatorCreateWiedervorlageButton); + public getCreateWiedervorlageButton(): Cypress.Chainable<Element> { + return cy.getTestElement(this.createWiedervorlageButton); } } diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/kommentar-attachment/kommentar-attachment.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/kommentar-attachment/kommentar-attachment.cy.ts index c85070d6378af981222dca9bbaf595db77d6fff5..8cddad300e40d3cb4c4cca590d69f30845a5314f 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/kommentar-attachment/kommentar-attachment.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/kommentar-attachment/kommentar-attachment.cy.ts @@ -26,17 +26,16 @@ import { AttachmentContainerE2EComponent, AttachmentListE2EComponent, } from '../../../components/attachment/attachment.e2e.component'; -import { KommentareInVorgangE2EComponent } from '../../../components/kommentar/kommentar-list.e2e.component'; +import { KommentarListInVorgangE2EComponent } from '../../../components/kommentar/kommentar-list.e2e.component'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; -import { UserE2E } from '../../../model/user'; import { VorgangE2E } from '../../../model/vorgang'; import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; import { VorgangPage } from '../../../page-objects/vorgang.po'; import { dropCollections, readFileFromDownloads } from '../../../support/cypress-helper'; import { exist, notExist } from '../../../support/cypress.util'; -import { TEST_FILE_WITHOUT_CONTENT, TEST_FILE_WITH_CONTENT } from '../../../support/data.util'; -import { uploadFile } from '../../../support/file-upload'; -import { getUserSabine, loginAsSabine } from '../../../support/user-util'; +import { TEST_FILE_WITH_CONTENT, TEST_FILE_WITHOUT_CONTENT } from '../../../support/data.util'; +import { uploadFile, uploadFiles } from '../../../support/file-upload'; +import { loginAsSabine } from '../../../support/user-util'; import { createVorgang, initVorgang } from '../../../support/vorgang-util'; describe('Kommentar attachments', () => { @@ -44,16 +43,14 @@ describe('Kommentar attachments', () => { const vorgangList: VorgangListE2EComponent = mainPage.getVorgangList(); const vorgangPage: VorgangPage = new VorgangPage(); - const kommentarContainer: KommentareInVorgangE2EComponent = vorgangPage.getKommentarContainer(); + const kommentarContainer: KommentarListInVorgangE2EComponent = vorgangPage.getKommentarContainer(); - const attachmentContainer: AttachmentContainerE2EComponent = - kommentarContainer.getAttachmentContainer(); + const attachmentContainer: AttachmentContainerE2EComponent = kommentarContainer.getAttachmentContainer(); const attachmentList: AttachmentListE2EComponent = attachmentContainer.getList(); const kommentarText: string = 'Test text to test the test text test'; const vorgang: VorgangE2E = createVorgang(); - const userSabine: UserE2E = getUserSabine(); before(() => { initVorgang(vorgang); @@ -128,20 +125,22 @@ describe('Kommentar attachments', () => { }); }); - describe('Download Kommentar attachments', () => { - it('should upload attachment', () => { + describe('upload multiple attachments', () => { + it('should show files after upload is done', () => { kommentarContainer.getKommentar(kommentarText).getRoot().click(); waitForSpinnerToDisappear(); - uploadFile(attachmentContainer.getUploadInput(), TEST_FILE_WITH_CONTENT); + uploadFiles(attachmentContainer.getUploadInput(), [TEST_FILE_WITH_CONTENT, TEST_FILE_WITHOUT_CONTENT]); waitForSpinnerToDisappear(); - kommentarContainer.getFormularSpeichernButton().click(); waitForSpinnerToDisappear(); exist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); + exist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); }); + }); + describe('Download Kommentar attachments', () => { it('should download uploaded attachment', () => { sleep(1000); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/kommentar/kommentar.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/kommentar/kommentar.cy.ts index 23754b15795ba5f5ed020b7dd450dad2a2d14fe3..002476fc9fbca92bc87ef43cf52f794266c3c45c 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/kommentar/kommentar.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/kommentar/kommentar.cy.ts @@ -21,13 +21,13 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { KommentareInVorgangE2EComponent } from '../../../components/kommentar/kommentar-list.e2e.component'; +import { KommentarListInVorgangE2EComponent } from '../../../components/kommentar/kommentar-list.e2e.component'; import { UserE2E } from '../../../model/user'; import { VorgangE2E } from '../../../model/vorgang'; import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; import { VorgangPage } from '../../../page-objects/vorgang.po'; import { dropCollections } from '../../../support/cypress-helper'; -import { contains, exist, haveText, notExist } from '../../../support/cypress.util'; +import { contains, exist, haveLength, haveText, notExist } from '../../../support/cypress.util'; import { getUserSabine, initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; import { createVorgang, initVorgang } from '../../../support/vorgang-util'; @@ -35,7 +35,7 @@ describe('Kommentar', () => { const mainPage: MainPage = new MainPage(); const vorgangPage: VorgangPage = new VorgangPage(); - const kommentarContainer: KommentareInVorgangE2EComponent = vorgangPage.getKommentarContainer(); + const kommentarContainer: KommentarListInVorgangE2EComponent = vorgangPage.getKommentarContainer(); const kommentarText: string = 'Test text to test the test text test'; @@ -90,7 +90,7 @@ describe('Kommentar', () => { }); describe('click on hinzufuegen button in formular', () => { - it('should show error on invalid input', () => { + it('should show error on empty text', () => { kommentarContainer.getFormularSpeichernButton().click(); waitForSpinnerToDisappear(); @@ -101,53 +101,22 @@ describe('Kommentar', () => { kommentarContainer.getTextInput().clear().type(kommentarText); kommentarContainer.getFormularSpeichernButton().click(); - exist(vorgangPage.getSpinner()); - notExist( - kommentarContainer - .getKommentar(kommentarText) - .getUserProfile() - .getIconContainer() - .getAssignedIcon(), - ); - exist(vorgangPage.getSpinner()); waitForSpinnerToDisappear(); - exist( - kommentarContainer - .getKommentar(kommentarText) - .getUserProfile() - .getIconContainer() - .getAssignedIcon(), - ); + exist(kommentarContainer.getKommentar(kommentarText).getUserProfile().getIconContainer().getAssignedIcon()); notExist(kommentarContainer.getTextError()); notExist(kommentarContainer.getFormular()); exist(kommentarContainer.getHinzufuegenButton()); - }); - - it('should show entry in list', () => { exist(kommentarContainer.getKommentar(kommentarText).getRoot()); }); it('should show user profile at kommentar', () => { - exist( - kommentarContainer - .getKommentar(kommentarText) - .getUserProfile() - .getIconContainer() - .getAssignedIcon(), - ); + exist(kommentarContainer.getKommentar(kommentarText).getUserProfile().getIconContainer().getAssignedIcon()); haveText( - kommentarContainer - .getKommentar(kommentarText) - .getUserProfile() - .getIconContainer() - .getAssignedIcon(), + kommentarContainer.getKommentar(kommentarText).getUserProfile().getIconContainer().getAssignedIcon(), userSabine.initials, ); - contains( - kommentarContainer.getKommentar(kommentarText).getUserProfile().getName(), - userSabine.fullName, - ); + contains(kommentarContainer.getKommentar(kommentarText).getUserProfile().getName(), userSabine.fullName); exist(kommentarContainer.getKommentar(kommentarText).getCreatedAt()); }); }); @@ -212,5 +181,27 @@ describe('Kommentar', () => { exist(kommentarContainer.getHinzufuegenButton()); }); }); + + describe('other kommentar', () => { + const otherKommentar: string = 'otherKommentar'; + + it('should be created', () => { + kommentarContainer.getHinzufuegenButton().click(); + kommentarContainer.getTextInput().clear().type(otherKommentar); + kommentarContainer.getFormularSpeichernButton().click(); + waitForSpinnerToDisappear(); + + haveLength(kommentarContainer.getFormulare(), 0); + exist(kommentarContainer.getKommentar(otherKommentar).getRoot()); + }); + + it('should hide previous opened kommentar on edit other kommentar', () => { + kommentarContainer.getKommentar(kommentarText).getRoot().click(); + haveLength(kommentarContainer.getFormulare(), 1); + + kommentarContainer.getKommentar(otherKommentar).getRoot().click(); + haveLength(kommentarContainer.getFormulare(), 1); + }); + }); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.cy.ts index 83ccb2b2cac8628bf8755dacc14c81a9acd7d65a..70ef41af32be8a87839358860fa5eb7c7243be28 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.cy.ts @@ -26,12 +26,11 @@ import { AttachmentContainerE2EComponent, AttachmentListE2EComponent, } from 'apps/alfa-e2e/src/components/attachment/attachment.e2e.component'; +import { E2EAttachmentHelper } from 'apps/alfa-e2e/src/helper/attachment/attachment.helper'; +import { E2EAttachmentVerifier } from 'apps/alfa-e2e/src/helper/attachment/attachment.verifier'; import { BinaryFileSnackbarMessageE2E } from 'apps/alfa-e2e/src/model/binary-file'; import { PostfachMailFormularE2EComponent } from '../../../components/postfach/postfach-mail-formular.e2e.component'; -import { - PostfachMailE2EComponent, - PostfachMailListItem, -} from '../../../components/postfach/postfach-mail.e2e.component'; +import { PostfachMailE2EComponent, PostfachMailListItem } from '../../../components/postfach/postfach-mail.e2e.component'; import { FixedDialogE2EComponent } from '../../../components/ui/fixed-dialog.e2e.component'; import { SnackBarE2EComponent } from '../../../components/ui/snackbar.e2e.component'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; @@ -48,21 +47,8 @@ import { PostfachMailPage } from '../../../page-objects/postfach-mail.component. import { VorgangPage } from '../../../page-objects/vorgang.po'; import { expectIconWithBadge, expectIconWithoutBadge } from '../../../support/angular.util'; import { dropCollections, readFileFromDownloads } from '../../../support/cypress-helper'; -import { - beChecked, - contains, - exist, - notBeChecked, - notBeVisible, - notExist, - visible, -} from '../../../support/cypress.util'; -import { - TEST_FILE_WITHOUT_CONTENT, - TEST_FILE_WITH_CONTENT, - TEST_FILE_WITH_CONTENT_4_MB, -} from '../../../support/data.util'; -import { uploadEmptyFile, uploadFile } from '../../../support/file-upload'; +import { beChecked, contains, exist, notBeChecked, notBeVisible, notExist, visible } from '../../../support/cypress.util'; +import { TEST_FILE_WITH_CONTENT, TEST_FILE_WITH_CONTENT_4_MB, TEST_FILE_WITHOUT_CONTENT } from '../../../support/data.util'; import { initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; import { createPostfachNachrichtAttachedItem, @@ -87,20 +73,19 @@ describe('PostfachMail', () => { const postfachMailContainer: PostfachMailE2EComponent = vorgangPage.getPostfachMailcontainer(); const subnavigation: VorgangSubnavigationE2EComponent = vorgangPage.getSubnavigation(); const fixedDialog: FixedDialogE2EComponent = vorgangPage.getFixedDialog(); - const postfachMailFormular: PostfachMailFormularE2EComponent = - vorgangPage.getPostfachMailFormular(); - const attachmentContainer: AttachmentContainerE2EComponent = - postfachMailFormular.getAttachmentContainer(); + const postfachMailFormular: PostfachMailFormularE2EComponent = vorgangPage.getPostfachMailFormular(); + const attachmentContainer: AttachmentContainerE2EComponent = postfachMailFormular.getAttachmentContainer(); const attachmentList: AttachmentListE2EComponent = attachmentContainer.getList(); + const attachmentHelper: E2EAttachmentHelper = new E2EAttachmentHelper(); + const attachmentVerifier: E2EAttachmentVerifier = new E2EAttachmentVerifier(); + const postfachMailPage: PostfachMailPage = new PostfachMailPage(); const clientAttributes: ClientAttributesE2E = { [VorgangAttachedItemClientE2E.OZGCLOUD_NACHRICHTEN_MANAGER]: { - [ClientAttributeNameE2E.HAS_NEW_POSTFACH_NACHRICHT]: - createHasNewPostfachNachrichtClientAttribute(true), - [ClientAttributeNameE2E.HAS_POSTFACH_NACHRICHT]: - createHasPostfachNachrichtClientAttribute(true), + [ClientAttributeNameE2E.HAS_NEW_POSTFACH_NACHRICHT]: createHasNewPostfachNachrichtClientAttribute(true), + [ClientAttributeNameE2E.HAS_POSTFACH_NACHRICHT]: createHasPostfachNachrichtClientAttribute(true), }, }; @@ -149,9 +134,7 @@ describe('PostfachMail', () => { describe('mail icon with badge', () => { it('should be visible and have a badge', () => { - const postfachStatusIcon: HTMLElement = vorgangList - .getListItem(vorgangWithReply.name) - .getPostfachIconMatIcon(); + const postfachStatusIcon: HTMLElement = vorgangList.getListItem(vorgangWithReply.name).getPostfachIconMatIcon(); expectIconWithBadge(postfachStatusIcon); }); }); @@ -266,65 +249,54 @@ describe('PostfachMail', () => { describe('attach files', () => { it('should show empty attachment after uploading it', () => { - uploadEmptyFile(attachmentContainer.getUploadInput(), TEST_FILE_WITHOUT_CONTENT); - waitForSpinnerToDisappear(); + attachmentHelper.uploadEmptyAttachment(); - exist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + attachmentVerifier.verifyAttachmentInList(TEST_FILE_WITHOUT_CONTENT); }); it('should show attachment with content after uploading it', () => { - uploadFile( - postfachMailFormular.getAttachmentContainer().getUploadInput(), - TEST_FILE_WITH_CONTENT, - ); - waitForSpinnerToDisappear(); + attachmentHelper.uploadAttachment(); - exist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); + attachmentVerifier.verifyAttachmentInList(TEST_FILE_WITH_CONTENT); }); it('should download empty attachment on click', () => { - attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getDownloadButton().click(); - waitForSpinnerToDisappear(); + attachmentHelper.downloadAttachment(TEST_FILE_WITHOUT_CONTENT); - exist(readFileFromDownloads(TEST_FILE_WITHOUT_CONTENT)); + attachmentVerifier.verifyAttachmentDownload(TEST_FILE_WITHOUT_CONTENT); }); it('should download attachment with content on click', () => { - attachmentList.getItem(TEST_FILE_WITH_CONTENT).getDownloadButton().click(); - waitForSpinnerToDisappear(); + attachmentHelper.downloadAttachment(TEST_FILE_WITH_CONTENT); - exist(readFileFromDownloads(TEST_FILE_WITH_CONTENT)); + attachmentVerifier.verifyAttachmentDownload(TEST_FILE_WITH_CONTENT); }); it('should not show empty attachment after deleting it', () => { - attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getDeleteButton().click(); - waitForSpinnerToDisappear(); + attachmentHelper.deleteAttachment(TEST_FILE_WITHOUT_CONTENT); + + attachmentVerifier.verifyAttachmentNotInList(TEST_FILE_WITHOUT_CONTENT); + }); + + it('should delete and multi upload all', () => { + attachmentHelper.deleteAttachments([TEST_FILE_WITH_CONTENT]); - notExist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + attachmentHelper.uploadAttachments([TEST_FILE_WITH_CONTENT, TEST_FILE_WITHOUT_CONTENT]); + + attachmentVerifier.verifyAttachmentsInList([TEST_FILE_WITH_CONTENT, TEST_FILE_WITHOUT_CONTENT]); }); }); describe('attach file > 3 MB', () => { - it('should show error snackbar', () => { - uploadFile( - postfachMailFormular.getAttachmentContainer().getUploadInput(), - TEST_FILE_WITH_CONTENT_4_MB, - ); - waitForSpinnerToDisappear(); + it('should show failed upload', { defaultCommandTimeout: 90000 }, () => { + attachmentHelper.uploadAttachment(TEST_FILE_WITH_CONTENT_4_MB); - exist(snackbar.getMessage()); + exist(attachmentList.getLoadingOrErrorItem(TEST_FILE_WITH_CONTENT_4_MB).getRoot()); contains( - snackbar.getMessage(), + attachmentList.getLoadingOrErrorItem(TEST_FILE_WITH_CONTENT_4_MB).getRoot(), BinaryFileSnackbarMessageE2E.ATTACHMENT_NOT_ADDED.replace('{size}', '3MB'), ); }); - - it('should close snackbar on close button', () => { - snackbar.getCloseButton().click(); - - notExist(snackbar.getCloseButton()); - notExist(snackbar.getMessage()); - }); }); describe('reply option button', () => { @@ -356,9 +328,7 @@ describe('PostfachMail', () => { }); it('should show postfach mail in list', () => { - const postfachMailItem: PostfachMailListItem = postfachMailContainer.getListItem( - postfachMailToSend.subject, - ); + const postfachMailItem: PostfachMailListItem = postfachMailContainer.getListItem(postfachMailToSend.subject); exist(postfachMailItem.getRoot()); exist(postfachMailItem.getUserProfile().getRoot()); @@ -370,9 +340,7 @@ describe('PostfachMail', () => { describe('click on postfach mail item with attachment', () => { it('should show postfach item list', () => { - const postfachMailItem: PostfachMailListItem = postfachMailContainer.getListItem( - postfachMailToSend.subject, - ); + const postfachMailItem: PostfachMailListItem = postfachMailContainer.getListItem(postfachMailToSend.subject); postfachMailItem.getRoot().click(); waitForSpinnerToDisappear(); @@ -380,34 +348,27 @@ describe('PostfachMail', () => { }); it('should contain mail item with attachment', () => { - const postfachListItem: PostfachMailListItem = postfachMailPage.getListItem( - postfachMailToSend.subject, - ); + const postfachListItem: PostfachMailListItem = postfachMailPage.getListItem(postfachMailToSend.subject); - exist( - postfachListItem - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_WITH_CONTENT) - .getRoot(), - ); + exist(postfachListItem.getAttachmentContainer().getList().getItem(TEST_FILE_WITH_CONTENT).getRoot()); }); it('should download attachment after click', () => { - const postfachListItem: PostfachMailListItem = postfachMailPage.getListItem( - postfachMailToSend.subject, - ); + const postfachListItem: PostfachMailListItem = postfachMailPage.getListItem(postfachMailToSend.subject); - postfachListItem - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_WITH_CONTENT) - .getDownloadButton() - .click(); + postfachListItem.getAttachmentContainer().getList().getItem(TEST_FILE_WITH_CONTENT).getDownloadButton().click(); waitForSpinnerToDisappear(); exist(readFileFromDownloads(TEST_FILE_WITHOUT_CONTENT)); }); + + it('should not contain failed upload', () => { + const postfachListItem: PostfachMailListItem = postfachMailPage.getListItem(postfachMailToSend.subject); + + notExist( + postfachListItem.getAttachmentContainer().getList().getLoadingOrErrorItem(TEST_FILE_WITH_CONTENT_4_MB).getRoot(), + ); + }); }); describe('click on back button', () => { @@ -451,10 +412,7 @@ describe('PostfachMail', () => { }); it('should show text for no postfach attached', () => { - contains( - postfachMailContainer.getNoPostfachText(), - 'Dieser Vorgang ist nicht mit einem Postfach verknüpft.', - ); + contains(postfachMailContainer.getNoPostfachText(), 'Dieser Vorgang ist nicht mit einem Postfach verknüpft.'); }); it('should navigate back to list', () => { @@ -498,10 +456,7 @@ describe('PostfachMail', () => { }); it('should show text', () => { - contains( - postfachMailContainer.getListItem(postfachMailReply.subject).getText(), - postfachMailReply.mailBody, - ); + contains(postfachMailContainer.getListItem(postfachMailReply.subject).getText(), postfachMailReply.mailBody); }); }); @@ -566,9 +521,7 @@ describe('PostfachMail', () => { }); it('should be visible and not have a badge', () => { - const postfachStatusIcon: HTMLElement = vorgangList - .getListItem(vorgangWithReply.name) - .getPostfachIconMatIcon(); + const postfachStatusIcon: HTMLElement = vorgangList.getListItem(vorgangWithReply.name).getPostfachIconMatIcon(); expectIconWithoutBadge(postfachStatusIcon); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/user-assistance/help-menu.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/user-assistance/help-menu.cy.ts index 4cc001a7f49f79190e8e1f53ef1242c36378d238..f2b5373adae2011b3bd3221041f783d5c8dd9bac 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/user-assistance/help-menu.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/user-assistance/help-menu.cy.ts @@ -25,7 +25,7 @@ import { HelpMenuE2EComponent } from 'apps/alfa-e2e/src/components/user-assistan import { HeaderE2EComponent } from 'apps/alfa-e2e/src/page-objects/header.po'; import { MainPage, waitForSpinnerToDisappear } from 'apps/alfa-e2e/src/page-objects/main.po'; import { dropCollections } from 'apps/alfa-e2e/src/support/cypress-helper'; -import { contains, exist, shouldHaveAttribute } from 'apps/alfa-e2e/src/support/cypress.util'; +import { exist } from 'apps/alfa-e2e/src/support/cypress.util'; import { loginAsSabine } from 'apps/alfa-e2e/src/support/user-util'; describe('Help Menu', () => { @@ -33,7 +33,7 @@ describe('Help Menu', () => { const header: HeaderE2EComponent = mainPage.getHeader(); const helpMenu: HelpMenuE2EComponent = header.getHelpMenu(); - const impressumLink: string = 'https://static.dev.sh.ozg-cloud.de/impressum' + const impressumLink: string = 'https://static.dev.sh.ozg-cloud.de/impressum'; before(() => { loginAsSabine(); @@ -50,7 +50,7 @@ describe('Help Menu', () => { it('should show "open documentation"', () => { helpMenu.getRoot().click(); - exist(helpMenu.getOpenDocumentationButton()); + exist(helpMenu.getDocumentation()); }); it('should show Impressum button and find link', () => { @@ -59,13 +59,7 @@ describe('Help Menu', () => { }); it('should open documentation', () => { - helpMenu - .getOpenDocumentationButton() - .find('a') - .invoke('removeAttr', 'target') - .click() - .url() - .should('include', 'benutzerleitfaden'); + helpMenu.getDocumentation().find('a').invoke('removeAttr', 'target').click().url().should('include', 'benutzerleitfaden'); }); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-anhang/vorgang-anhang-herunterladen.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-anhang/vorgang-anhang-herunterladen.cy.ts index 66ed9474b72739851ea9c6f0eb4845b761d91377..122141ad34b4cb0fbf1b594b071ffaa932e9166f 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-anhang/vorgang-anhang-herunterladen.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-anhang/vorgang-anhang-herunterladen.cy.ts @@ -132,7 +132,7 @@ describe('Vorgang Anhänge', () => { }); it('should download attachment zip file', () => { - attachmentList.downloadAttachments(); + attachmentList.getDownloadArchiveButton().click(); waitForSpinnerToDisappear(); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-abbrechen.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-abbrechen.cy.ts index 7292aa333e4233b18c846485f02d316a080b6dc4..d7f9e7a5a73f9fa0eb942be9e5b64b25e4f53169 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-abbrechen.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-abbrechen.cy.ts @@ -88,9 +88,9 @@ describe('Vorgang - Bescheid abbrechen', () => { it('should upload files and continue to step 3', () => { bescheidWizard.uploadBescheid(TEST_FILE_BESCHEID_VALID); - bescheidWizard.bescheidUploadSpinnerIsClosed(); + waitForSpinnerToDisappear(); bescheidWizard.uploadAttachment(TEST_FILE_BESCHEID_ANHANG_VALID); - bescheidWizard.attachmentSpinnerIsClosed(); + waitForSpinnerToDisappear(); bescheidWizard.weiter(); bescheidWizard.isBescheidVersendenStep(); @@ -140,9 +140,10 @@ describe('Vorgang - Bescheid abbrechen', () => { it('should upload Bescheid and attachment', () => { uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); + uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_BESCHEID_ANHANG_VALID); - notExist(bescheidWizard.getAttachmentUploadSpinner()); + waitForSpinnerToDisappear(); }); it('should open dialog after click on X', () => { bescheidWizard.close(); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-automatisch-erstellen.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-automatisch-erstellen.cy.ts index 7cd0331269d087f962a1602f91a87b27ddf14128..1a8e2f654385e9d00a147f3d87277d3d4c175686 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-automatisch-erstellen.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-automatisch-erstellen.cy.ts @@ -28,12 +28,7 @@ import { VorgangBescheidWizardE2EComponent } from 'apps/alfa-e2e/src/components/ import { VorgangFormularButtonsE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-formular-buttons.e2e.components'; import { VorgangSubnavigationE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-subnavigation'; import { SmockerMocks } from 'apps/alfa-e2e/src/model/smocker'; -import { - EingangE2E, - EingangHeaderE2E, - VorgangE2E, - VorgangStatusE2E, -} from 'apps/alfa-e2e/src/model/vorgang'; +import { EingangE2E, EingangHeaderE2E, VorgangE2E, VorgangStatusE2E } from 'apps/alfa-e2e/src/model/vorgang'; import 'cypress-real-events/support'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; @@ -41,12 +36,7 @@ import { VorgangPage } from '../../../page-objects/vorgang.po'; import { addSmockerMock, dropCollections } from '../../../support/cypress-helper'; import { exist, haveText, notExist } from '../../../support/cypress.util'; import { initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; -import { - buildVorgang, - createVorgang, - initVorgaenge, - objectIds, -} from '../../../support/vorgang-util'; +import { buildVorgang, createVorgang, initVorgaenge, objectIds } from '../../../support/vorgang-util'; registerLocaleData(localeDe, 'de', localeDeExtra); @@ -79,8 +69,7 @@ describe('Upload automatic Bescheid', () => { status: VorgangStatusE2E.IN_BEARBEITUNG, }; - const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = - vorgangPage.getFormularButtons(); + const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = vorgangPage.getFormularButtons(); const vorgangSubnavigation: VorgangSubnavigationE2EComponent = vorgangPage.getSubnavigation(); @@ -114,8 +103,7 @@ describe('Upload automatic Bescheid', () => { waitForSpinnerToDisappear(); bescheidWizard.getUploadAutomaticBescheidButton().click(); - exist(bescheidWizard.getBescheidUploadSpinner()); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); exist(bescheidWizard.getAutomaticBescheidFileInWizard()); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-dokumente-hochladen.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-dokumente-hochladen.cy.ts index 2f0235885f20f005854b6a72df819534cbd15c6c..71531d122d7f3fdc14570feb7d498dd7f449b467 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-dokumente-hochladen.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-dokumente-hochladen.cy.ts @@ -26,15 +26,25 @@ import localeDe from '@angular/common/locales/de'; import localeDeExtra from '@angular/common/locales/extra/de'; import { VorgangBescheidWizardE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-bescheid-wizard.e2e.component'; import { VorgangFormularButtonsE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-formular-buttons.e2e.components'; +import { E2EAttachmentHelper } from 'apps/alfa-e2e/src/helper/attachment/attachment.helper'; +import { E2EAttachmentVerifier } from 'apps/alfa-e2e/src/helper/attachment/attachment.verifier'; import { VorgangE2E, VorgangStatusE2E } from 'apps/alfa-e2e/src/model/vorgang'; import { uploadFile } from 'apps/alfa-e2e/src/support/file-upload'; import 'cypress-real-events/support'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; import { VorgangPage } from '../../../page-objects/vorgang.po'; -import { dropCollections, getTestElement } from '../../../support/cypress-helper'; +import { dropCollections } from '../../../support/cypress-helper'; import { contains, exist, haveText, haveValue, notContains, notExist } from '../../../support/cypress.util'; -import { TEST_FILE_BESCHEID_ANHANG_BIG, TEST_FILE_BESCHEID_ANHANG_VALID, TEST_FILE_BESCHEID_VALID, TEST_FILE_JPEG, TEST_FILE_JPG, TEST_FILE_PNG, TEST_FILE_WITH_CONTENT, } from '../../../support/data.util'; +import { + TEST_FILE_BESCHEID_ANHANG_BIG, + TEST_FILE_BESCHEID_ANHANG_VALID, + TEST_FILE_BESCHEID_VALID, + TEST_FILE_JPEG, + TEST_FILE_JPG, + TEST_FILE_PNG, + TEST_FILE_WITH_CONTENT, +} from '../../../support/data.util'; import { initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; import { buildVorgang, initVorgaenge, objectIds } from '../../../support/vorgang-util'; @@ -54,6 +64,9 @@ describe('Bescheid Dokumente hochladen', () => { const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = vorgangPage.getFormularButtons(); + const attachmentHelper: E2EAttachmentHelper = new E2EAttachmentHelper(); + const attachmentVerifier: E2EAttachmentVerifier = new E2EAttachmentVerifier(); + const documentError: string = 'Erlaubte Dateiendungen'; const sizeError: string = 'Anhänge größer'; const missingBescheidError: string = 'Bitte fügen Sie ein Bescheiddokument hinzu.'; @@ -90,13 +103,15 @@ describe('Bescheid Dokumente hochladen', () => { it('should upload manual Bescheid file', () => { uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); + exist(bescheidWizard.getFileBescheidValidInWizard()); }); it('should upload attachment file', () => { - uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_BESCHEID_ANHANG_VALID); - exist(bescheidWizard.getFileAnhangValidInWizard()); + attachmentHelper.uploadAttachment(TEST_FILE_BESCHEID_ANHANG_VALID); + + attachmentVerifier.verifyAttachmentInList(TEST_FILE_BESCHEID_ANHANG_VALID); }); it('should still show files after clicking Weiter and step 2', () => { @@ -127,15 +142,9 @@ describe('Bescheid Dokumente hochladen', () => { }); it('should be able to attach all valid types of files', () => { - uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_JPG); - notExist(bescheidWizard.getAttachmentUploadSpinner()); - uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_JPEG); - notExist(bescheidWizard.getAttachmentUploadSpinner()); - uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_PNG); - notExist(bescheidWizard.getAttachmentUploadSpinner()); - exist(getTestElement(bescheidWizard.getElementFromFileName(TEST_FILE_JPG))); - exist(getTestElement(bescheidWizard.getElementFromFileName(TEST_FILE_JPEG))); - exist(getTestElement(bescheidWizard.getElementFromFileName(TEST_FILE_PNG))); + attachmentHelper.uploadAttachments([TEST_FILE_JPG, TEST_FILE_JPEG, TEST_FILE_PNG]); + + attachmentVerifier.verifyAttachmentsInList([TEST_FILE_JPG, TEST_FILE_JPEG, TEST_FILE_PNG]); }); }); @@ -145,8 +154,9 @@ describe('Bescheid Dokumente hochladen', () => { contains(bescheidWizard.getBescheidDocument(), documentError); }); - it('should show error if file is too big', () => { + it('should show error if file is too big', { defaultCommandTimeout: 90000 }, () => { uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_BESCHEID_ANHANG_BIG); + contains(bescheidWizard.getAttachmentDocument(), sizeError); }); }); @@ -162,7 +172,7 @@ describe('Bescheid Dokumente hochladen', () => { describe('check contents of step 3', () => { it('should show Max Testermann as Antragsteller, the default message text', () => { uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); bescheidWizard.getWeiterButton().click(); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-historie.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-historie.cy.ts index 0507e30112e6638ca225665737c64002a6ec99f0..55c6b8b418ee3224b35210e6b963dbf78e03d268 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-historie.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-historie.cy.ts @@ -29,7 +29,10 @@ import { VorgangBescheideE2EComponent } from 'apps/alfa-e2e/src/components/vorga import { VorgangFormularButtonsE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-formular-buttons.e2e.components'; import { VorgangFormularDatenE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-formular.e2e.component'; import { VorgangSubnavigationE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-subnavigation'; -import { BescheidHistorieItemE2EComponent, VorgangFormularDatenHistorieItemE2EComponent, } from 'apps/alfa-e2e/src/components/vorgang/vorgang.formular-daten.historie.e2e.component'; +import { + BescheidHistorieItemE2EComponent, + VorgangFormularDatenHistorieItemE2EComponent, +} from 'apps/alfa-e2e/src/components/vorgang/vorgang.formular-daten.historie.e2e.component'; import { HistorieHeadlineE2E } from 'apps/alfa-e2e/src/model/historie'; import { VorgangE2E, VorgangStatusE2E } from 'apps/alfa-e2e/src/model/vorgang'; import { TEST_FILE_BESCHEID_ANHANG_VALID, TEST_FILE_BESCHEID_VALID } from 'apps/alfa-e2e/src/support/data.util'; @@ -39,7 +42,7 @@ import 'cypress-real-events/support'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; import { VorgangPage } from '../../../page-objects/vorgang.po'; -import { dropCollections } from '../../../support/cypress-helper'; +import { dropCollections, wait } from '../../../support/cypress-helper'; import { contains, enterWith, exist, notExist } from '../../../support/cypress.util'; import { getUserSabine, initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; import { buildVorgang, initVorgaenge, objectIds } from '../../../support/vorgang-util'; @@ -47,6 +50,7 @@ import { buildVorgang, initVorgaenge, objectIds } from '../../../support/vorgang registerLocaleData(localeDe, 'de', localeDeExtra); describe('Bescheid History', () => { + const waitForFormPatchMillis: number = 100; const mainPage: MainPage = new MainPage(); const vorgangList: VorgangListE2EComponent = mainPage.getVorgangList(); @@ -106,14 +110,20 @@ describe('Bescheid History', () => { vorgangFormularButtons.getBescheidenButton().click(); enterWith(bescheidWizard.getDateInput(), getAdjustedDateGerman(-1)); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); + uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_BESCHEID_ANHANG_VALID); - notExist(bescheidWizard.getAttachmentUploadSpinner()); + waitForSpinnerToDisappear(); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); + wait(waitForFormPatchMillis, 'Wait for patch form values to be completed.'); + bescheidWizard.getSaveButton().click(); bescheidWizard.getConfirmAndSaveButton().click(); waitForSpinnerToDisappear(); + vorgangDatenFormular.getHistorieTab().click(); const historieItem: VorgangFormularDatenHistorieItemE2EComponent = vorgangDatenFormular.getHistorieItemByIndex(0); @@ -141,12 +151,18 @@ describe('Bescheid History', () => { vorgangFormularButtons.getBescheidenButton().click(); enterWith(bescheidWizard.getDateInput(), getAdjustedDateGerman(-1)); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); + uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); + wait(waitForFormPatchMillis, 'Wait for patch form values to be completed.'); + bescheidWizard.getSaveButton().click(); bescheidWizard.getConfirmAndSaveButton().click(); waitForSpinnerToDisappear(); + vorgangDatenFormular.getHistorieTab().click(); const historieItem: VorgangFormularDatenHistorieItemE2EComponent = vorgangDatenFormular.getHistorieItemByIndex(0); @@ -170,16 +186,19 @@ describe('Bescheid History', () => { bescheidWizard.getAbgelehntButton().click(); enterWith(bescheidWizard.getDateInput(), getAdjustedDateGerman(1)); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); + uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_BESCHEID_ANHANG_VALID); - notExist(bescheidWizard.getAttachmentUploadSpinner()); + waitForSpinnerToDisappear(); uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_BESCHEID_ANHANG_VALID); - notExist(bescheidWizard.getAttachmentUploadSpinner()); + waitForSpinnerToDisappear(); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); + bescheidWizard.getSendButton().click(); - exist(bescheidWizard.getSendenSpinner()); - notExist(bescheidWizard.getSendenSpinner()); + waitForSpinnerToDisappear(); vorgangDatenFormular.getHistorieTab().click(); const historieItemExpand: VorgangFormularDatenHistorieItemE2EComponent = vorgangDatenFormular.getHistorieItemByIndex(0); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-info-anzeigen.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-info-anzeigen.cy.ts index 6e602187c7712d336ea0cc1532cab9cab587bd52..d0e5d9939d952ad91aad4b1539b7989decf1f00b 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-info-anzeigen.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-info-anzeigen.cy.ts @@ -28,36 +28,15 @@ import { VorgangBescheidWizardE2EComponent } from 'apps/alfa-e2e/src/components/ import { VorgangBescheideE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-bescheide.e2e.component'; import { VorgangFormularButtonsE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-formular-buttons.e2e.components'; import { VorgangSubnavigationE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-subnavigation'; -import { - VorgangE2E, - VorgangStatusE2E, - vorgangStatusLabelE2E, -} from 'apps/alfa-e2e/src/model/vorgang'; -import { - TEST_FILE_BESCHEID_ANHANG_VALID, - TEST_FILE_BESCHEID_VALID, -} from 'apps/alfa-e2e/src/support/data.util'; +import { VorgangE2E, VorgangStatusE2E, vorgangStatusLabelE2E } from 'apps/alfa-e2e/src/model/vorgang'; +import { TEST_FILE_BESCHEID_ANHANG_VALID, TEST_FILE_BESCHEID_VALID } from 'apps/alfa-e2e/src/support/data.util'; import { getAdjustedDateGerman } from 'apps/alfa-e2e/src/support/tech.util'; import 'cypress-real-events/support'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; import { VorgangPage } from '../../../page-objects/vorgang.po'; -import { - countDownloadFiles, - deleteDownloadFolder, - dropCollections, - readFileFromDownloads, -} from '../../../support/cypress-helper'; -import { - contains, - enterWith, - exist, - haveLength, - haveText, - notBeVisible, - notExist, - visible, -} from '../../../support/cypress.util'; +import { countDownloadFiles, deleteDownloadFolder, dropCollections, readFileFromDownloads, } from '../../../support/cypress-helper'; +import { contains, enterWith, exist, haveLength, haveText, notBeVisible, notExist, visible } from '../../../support/cypress.util'; import { initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; import { buildVorgang, initVorgaenge, objectIds } from '../../../support/vorgang-util'; @@ -79,11 +58,9 @@ describe('Bescheid Info anzeigen', () => { status: VorgangStatusE2E.IN_BEARBEITUNG, }; - const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = - vorgangPage.getFormularButtons(); + const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = vorgangPage.getFormularButtons(); - const vorgangSubnavigationButtons: VorgangSubnavigationE2EComponent = - vorgangPage.getSubnavigation(); + const vorgangSubnavigationButtons: VorgangSubnavigationE2EComponent = vorgangPage.getSubnavigation(); const bescheide: VorgangBescheideE2EComponent = vorgangPage.getBescheide(); @@ -129,10 +106,7 @@ describe('Bescheid Info anzeigen', () => { }); it('should show status In Bearbeitung', () => { - haveText( - vorgangPage.getVorgangDetailHeader().getStatus(), - vorgangStatusLabelE2E[VorgangStatusE2E.IN_BEARBEITUNG], - ); + haveText(vorgangPage.getVorgangDetailHeader().getStatus(), vorgangStatusLabelE2E[VorgangStatusE2E.IN_BEARBEITUNG]); }); it('should show Bescheid as Entwurf with abgelehnt status and date yesterday', () => { @@ -149,6 +123,8 @@ describe('Bescheid Info anzeigen', () => { describe('should show complete info after saving Bescheid', () => { it('should set date to two days before and continue to step 2', () => { vorgangFormularButtons.getBescheidenButton().click(); + waitForSpinnerToDisappear(); + enterWith(bescheidWizard.getDateInput(), getAdjustedDateGerman(-2)); bescheidWizard.getWeiterButton().click(); bescheidWizard.isBescheidDocumentsStep(); @@ -156,9 +132,9 @@ describe('Bescheid Info anzeigen', () => { it('should upload files and continue to step 3', () => { bescheidWizard.uploadBescheid(TEST_FILE_BESCHEID_VALID); - bescheidWizard.bescheidUploadSpinnerIsClosed(); + waitForSpinnerToDisappear(); bescheidWizard.uploadAttachment(TEST_FILE_BESCHEID_ANHANG_VALID); - bescheidWizard.attachmentSpinnerIsClosed(); + waitForSpinnerToDisappear(); bescheidWizard.weiter(); bescheidWizard.isBescheidVersendenStep(); @@ -217,7 +193,7 @@ describe('Bescheid Info anzeigen', () => { it('should upload Bescheid and continue to step 3', () => { bescheidWizard.uploadBescheid(TEST_FILE_BESCHEID_VALID); - bescheidWizard.bescheidUploadSpinnerIsClosed(); + waitForSpinnerToDisappear(); bescheidWizard.weiter(); bescheidWizard.isBescheidVersendenStep(); }); @@ -225,9 +201,7 @@ describe('Bescheid Info anzeigen', () => { it('should show 2 Bescheid containers after saving', () => { bescheidWizard.getSaveButton().click(); bescheidWizard.getConfirmAndSaveButton().click(); - exist(bescheidWizard.getBescheidSaveSpinner()); - notExist(bescheidWizard.getBescheidSaveSpinner()); - + waitForSpinnerToDisappear(); haveLength(bescheide.getBescheidContainer(), 2); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-nur-speichern.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-nur-speichern.cy.ts index d2e2df9695234a26ff907b7cdde54563df9ca977..54c4965b9fb8e41f6cced9749f375e472bc5a262 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-nur-speichern.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-nur-speichern.cy.ts @@ -33,7 +33,7 @@ import 'cypress-real-events/support'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; import { VorgangPage } from '../../../page-objects/vorgang.po'; -import { dropCollections } from '../../../support/cypress-helper'; +import { dropCollections, wait } from '../../../support/cypress-helper'; import { contains, enterWith, exist, haveText, notExist } from '../../../support/cypress.util'; import { TEST_FILE_BESCHEID_ANHANG_VALID, TEST_FILE_BESCHEID_VALID } from '../../../support/data.util'; import { initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; @@ -42,6 +42,7 @@ import { buildVorgang, initVorgaenge, objectIds } from '../../../support/vorgang registerLocaleData(localeDe, 'de', localeDeExtra); describe('Bescheid speichern', () => { + const waitForFormPatchMillis: number = 100; const mainPage: MainPage = new MainPage(); const vorgangList: VorgangListE2EComponent = mainPage.getVorgangList(); @@ -82,10 +83,13 @@ describe('Bescheid speichern', () => { enterWith(bescheidWizard.getDateInput(), getAdjustedDateGerman(-1)); bescheidWizard.getWeiterButton().click(); uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_BESCHEID_ANHANG_VALID); - notExist(bescheidWizard.getAttachmentUploadSpinner()); + waitForSpinnerToDisappear(); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); + wait(waitForFormPatchMillis, 'Wait for patch form values to be completed.'); + bescheidWizard.getSaveButton().click(); contains(bescheidWizard.getStatusText(), abgelehntText + ' ' + getAdjustedDateGerman(-1)); @@ -97,9 +101,9 @@ describe('Bescheid speichern', () => { it('should update Vorgang status after saving', () => { bescheidWizard.getConfirmAndSaveButton().click(); - notExist(bescheidWizard.getRoot()); - waitForSpinnerToDisappear(); + + notExist(bescheidWizard.getRoot()); haveText(vorgangPage.getVorgangDetailHeader().getStatus(), vorgangStatusLabelE2E[VorgangStatusE2E.BESCHIEDEN]); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-senden.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-senden.cy.ts index 7d5ccdabee70ce2b9e5307e61cf20b79d18919f5..43e23febb7f15a82dd1431890c52590438388cfd 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-senden.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-senden.cy.ts @@ -32,10 +32,7 @@ import { VorgangBescheidWizardE2EComponent } from 'apps/alfa-e2e/src/components/ import { VorgangFormularButtonsE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-formular-buttons.e2e.components'; import { VorgangE2E, VorgangStatusE2E } from 'apps/alfa-e2e/src/model/vorgang'; import { PostfachMailPage } from 'apps/alfa-e2e/src/page-objects/postfach-mail.component.po'; -import { - TEST_FILE_BESCHEID_ANHANG_VALID, - TEST_FILE_BESCHEID_VALID, -} from 'apps/alfa-e2e/src/support/data.util'; +import { TEST_FILE_BESCHEID_ANHANG_VALID, TEST_FILE_BESCHEID_VALID } from 'apps/alfa-e2e/src/support/data.util'; import { uploadFile } from 'apps/alfa-e2e/src/support/file-upload'; import { getAdjustedDateGerman } from 'apps/alfa-e2e/src/support/tech.util'; import 'cypress-real-events/support'; @@ -43,7 +40,7 @@ import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-lis import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; import { VorgangPage } from '../../../page-objects/vorgang.po'; import { dropCollections } from '../../../support/cypress-helper'; -import { contains, enterWith, exist, notExist } from '../../../support/cypress.util'; +import { contains, enterWith, exist } from '../../../support/cypress.util'; import { initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; import { buildVorgang, initVorgaenge, objectIds } from '../../../support/vorgang-util'; @@ -66,8 +63,7 @@ describe('Bescheid senden', () => { status: VorgangStatusE2E.IN_BEARBEITUNG, }; - const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = - vorgangPage.getFormularButtons(); + const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = vorgangPage.getFormularButtons(); before(() => { initVorgaenge([bescheidSendenVorgang]); @@ -93,9 +89,9 @@ describe('Bescheid senden', () => { enterWith(bescheidWizard.getDateInput(), getAdjustedDateGerman(1)); bescheidWizard.getWeiterButton().click(); uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); uploadFile(bescheidWizard.getUploadAttachmentButton(), TEST_FILE_BESCHEID_ANHANG_VALID); - notExist(bescheidWizard.getAttachmentUploadSpinner()); + waitForSpinnerToDisappear(); bescheidWizard.getWeiterButton().click(); bescheidWizard.getSendButton().click(); @@ -114,21 +110,9 @@ describe('Bescheid senden', () => { const postfachListItem: PostfachMailListItem = postfachMailPage.getListItem(mailText); contains(postfachMailPage.getMailText(), anredeText); - exist( - postfachListItem - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_BESCHEID_VALID) - .getRoot(), - ); - - exist( - postfachListItem - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_BESCHEID_ANHANG_VALID) - .getRoot(), - ); + exist(postfachListItem.getAttachmentContainer().getList().getItem(TEST_FILE_BESCHEID_VALID).getRoot()); + + exist(postfachListItem.getAttachmentContainer().getList().getItem(TEST_FILE_BESCHEID_ANHANG_VALID).getRoot()); }); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-wizard.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-wizard.cy.ts index b256b6cb77b54936821445006e12a276584b457b..06378019b3ceaf1a1d74e8143846923091b7c2a0 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-wizard.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-bescheid/vorgang-bescheid-wizard.cy.ts @@ -26,11 +26,7 @@ import localeDe from '@angular/common/locales/de'; import localeDeExtra from '@angular/common/locales/extra/de'; import { VorgangBescheidWizardE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-bescheid-wizard.e2e.component'; import { VorgangFormularButtonsE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-formular-buttons.e2e.components'; -import { - VorgangE2E, - VorgangStatusE2E, - vorgangStatusLabelE2E, -} from 'apps/alfa-e2e/src/model/vorgang'; +import { VorgangE2E, VorgangStatusE2E, vorgangStatusLabelE2E } from 'apps/alfa-e2e/src/model/vorgang'; import { uploadFile } from 'apps/alfa-e2e/src/support/file-upload'; import 'cypress-real-events/support'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; @@ -63,8 +59,7 @@ describe('Bescheid Wizard', () => { status: VorgangStatusE2E.IN_BEARBEITUNG, }; - const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = - vorgangPage.getFormularButtons(); + const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = vorgangPage.getFormularButtons(); before(() => { initVorgaenge([wizardVorgang]); @@ -105,6 +100,7 @@ describe('Bescheid Wizard', () => { enterWith(bescheidWizard.getDateInput(), getAdjustedDateEnglish(-1)); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); exist(bescheidWizard.getDateError()); enterWith(bescheidWizard.getDateInput(), getAdjustedDateGerman(-1)); @@ -116,13 +112,15 @@ describe('Bescheid Wizard', () => { bescheidWizard.getRoot().should('not.contain', stepCaption3); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); bescheidWizard.getRoot().contains(stepCaption1); bescheidWizard.getRoot().contains(stepCaption2); bescheidWizard.getRoot().should('not.contain', stepCaption3); uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); bescheidWizard.getRoot().contains(stepCaption1); bescheidWizard.getRoot().contains(stepCaption2); bescheidWizard.getRoot().contains(stepCaption3); @@ -143,6 +141,7 @@ describe('Bescheid Wizard', () => { it('should close Wizard on Überspringen, discard data and set status to Abgeschlossen', () => { bescheidWizard.getAbgelehntButton().click(); bescheidWizard.getWeiterButton().click(); + waitForSpinnerToDisappear(); bescheidWizard.getStepButton1().click(); bescheidWizard.getUeberspringenButton().click(); exist(bescheidWizard.getUeberspringenDialog()); @@ -152,13 +151,11 @@ describe('Bescheid Wizard', () => { bescheidWizard.getUeberspringenButton().click(); bescheidWizard.getUeberspringenAbschliessen().click(); + waitForSpinnerToDisappear(); notExist(bescheidWizard.getRoot()); wait(1000); - haveText( - vorgangPage.getVorgangDetailHeader().getStatus(), - vorgangStatusLabelE2E[VorgangStatusE2E.ABGESCHLOSSEN], - ); + haveText(vorgangPage.getVorgangDetailHeader().getStatus(), vorgangStatusLabelE2E[VorgangStatusE2E.ABGESCHLOSSEN]); vorgangFormularButtons.getWiedereroeffnenButton().click(); vorgangFormularButtons.getBescheidenButton().click(); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-detailansicht/vorgang-dateien-tab.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-detailansicht/vorgang-dateien-tab.cy.ts index c93d89d9e08a0433aa15dc52a59e02b8a848f3c1..c1b5866cb7d4483cae346c7a3b036ae99654975c 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-detailansicht/vorgang-dateien-tab.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-detailansicht/vorgang-dateien-tab.cy.ts @@ -140,7 +140,7 @@ describe('Dateien Tab', () => { it('should show attachments and download button', () => { exist(vorgangDatenFormular.getFileElementByName(jpgFileName)); exist(vorgangDatenFormular.getFileElementByName(pdfFileName)); - exist(attachmentList.getDownloadAttachmentsButton(vorgangDatenFormular.getRoot())); + exist(attachmentList.getDownloadArchiveButton()); }); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-xdomea/vorgang-xdomea-inhalte.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-xdomea/vorgang-xdomea-inhalte.cy.ts index 1ed205ce1acecae3028a6682411de030b49c17cb..ff177ddcffc8fae0d04a2294b6c3176c07062abd 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-xdomea/vorgang-xdomea-inhalte.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-xdomea/vorgang-xdomea-inhalte.cy.ts @@ -36,10 +36,7 @@ import { VorgangAttachedItemE2E } from 'apps/alfa-e2e/src/model/vorgang-attached import { buildCommand, initCommands } from 'apps/alfa-e2e/src/support/command-util'; import { TEST_FILE_BESCHEID_VALID } from 'apps/alfa-e2e/src/support/data.util'; import { uploadFile } from 'apps/alfa-e2e/src/support/file-upload'; -import { - createKommentar, - createKommentarAttachedItem, -} from 'apps/alfa-e2e/src/support/kommentar.util'; +import { createKommentar, createKommentarAttachedItem } from 'apps/alfa-e2e/src/support/kommentar.util'; import { initVorgangAttachedItem } from 'apps/alfa-e2e/src/support/vorgang-attached-item-util'; import { objectIds } from 'apps/alfa-e2e/src/support/vorgang-util'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; @@ -53,7 +50,7 @@ import { getDownloadFiles, unzipDownloadFile, } from '../../../support/cypress-helper'; -import { exist, notExist } from '../../../support/cypress.util'; +import { exist } from '../../../support/cypress.util'; import { parseXml } from '../../../support/tech.util'; import { getUserDorotheaId, initUsermanagerUsers, loginAsSabine } from '../../../support/user-util'; import { createVorgang, initVorgang } from '../../../support/vorgang-util'; @@ -81,11 +78,7 @@ describe('check xDomea contents', () => { }; const assignUserCommand: CommandE2E = { - ...buildCommand( - CommandOrderE2E.ASSIGN_USER, - vorgangExportieren._id.$oid, - vorgangExportieren._id.$oid, - ), + ...buildCommand(CommandOrderE2E.ASSIGN_USER, vorgangExportieren._id.$oid, vorgangExportieren._id.$oid), bodyObject: { assignedTo: getUserDorotheaId() }, finishedAt: { $date: '2024-06-20T07:25:30.000Z' }, }; @@ -102,11 +95,7 @@ describe('check xDomea contents', () => { }; const setAktenzeichenCommand: CommandE2E = { - ...buildCommand( - CommandOrderE2E.SET_AKTENZEICHEN, - vorgangExportieren._id.$oid, - vorgangExportieren._id.$oid, - ), + ...buildCommand(CommandOrderE2E.SET_AKTENZEICHEN, vorgangExportieren._id.$oid, vorgangExportieren._id.$oid), order: CommandOrderE2E.SET_AKTENZEICHEN, bodyObject: { aktenzeichen: 'AKT_ENZ_EIC_HEN1' }, finishedAt: { $date: '2024-06-19T07:25:30.000Z' }, @@ -121,8 +110,7 @@ describe('check xDomea contents', () => { item: kommentar, }; - const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = - vorgangPage.getFormularButtons(); + const vorgangFormularButtons: VorgangFormularButtonsE2EComponent = vorgangPage.getFormularButtons(); const bescheidWizard: VorgangBescheidWizardE2EComponent = vorgangPage.getBescheidWizard(); @@ -148,7 +136,7 @@ describe('check xDomea contents', () => { vorgangFormularButtons.getBescheidenButton().click(); bescheidWizard.weiter(); uploadFile(bescheidWizard.getUploadBescheidButton(), TEST_FILE_BESCHEID_VALID); - notExist(bescheidWizard.getBescheidUploadSpinner()); + waitForSpinnerToDisappear(); bescheidWizard.weiter(); bescheidWizard.getSendButton().click(); waitForSpinnerToDisappear(); @@ -237,12 +225,7 @@ describe('check xDomea contents', () => { return getXMLFromTagAtPosition(xdomeaNamespace, content, tagName, position); } - function getXMLFromTagAtPosition( - namespaceURI: string, - content: string, - tagName: string, - position: number, - ) { + function getXMLFromTagAtPosition(namespaceURI: string, content: string, tagName: string, position: number) { const xmlDoc = parseXml(content); return xmlDoc.getElementsByTagNameNS(namespaceURI, tagName)[position].textContent; } @@ -273,9 +256,7 @@ describe('check xDomea contents', () => { function compareXmlEntry(xDomeaFile: string, xmlMap: Map<XmlTypePosition, string>) { xmlMap.forEach((expectedValue, xmlTypePosition) => { - expect( - getXdomeaNamespace(xDomeaFile, xmlTypePosition.type, xmlTypePosition.position), - ).to.equal(expectedValue); + expect(getXdomeaNamespace(xDomeaFile, xmlTypePosition.type, xmlTypePosition.position)).to.equal(expectedValue); }); } diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/wiedervorlage-attachment/wiedervorlage-attachment.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/wiedervorlage-attachment/wiedervorlage-attachment.cy.ts index 753c03ac4bec195c51eef3dffe2de7058e58ccb1..fa176831377d0b9775f4991bdaa1ccd2454df68a 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/wiedervorlage-attachment/wiedervorlage-attachment.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/wiedervorlage-attachment/wiedervorlage-attachment.cy.ts @@ -26,42 +26,25 @@ import { AttachmentListE2EComponent, } from 'apps/alfa-e2e/src/components/attachment/attachment.e2e.component'; import { WiedervorlageSubnavigationE2EComponent } from 'apps/alfa-e2e/src/components/wiedervorlage/wiedervorlage-subnavigation'; -import { BinaryFileSnackbarMessageE2E } from 'apps/alfa-e2e/src/model/binary-file'; +import { E2EAttachmentHelper } from 'apps/alfa-e2e/src/helper/attachment/attachment.helper'; +import { E2EWiedervorlageHelper } from 'apps/alfa-e2e/src/helper/wiedervorlage/wiedervorlage.helper'; import { WiedervorlageE2E } from 'apps/alfa-e2e/src/model/wiedervorlage'; -import { - dropCollections, - readFileFromDownloads, - wait, -} from 'apps/alfa-e2e/src/support/cypress-helper'; +import { dropCollections, readFileFromDownloads } from 'apps/alfa-e2e/src/support/cypress-helper'; import { initVorgangAttachedItem } from 'apps/alfa-e2e/src/support/vorgang-attached-item-util'; import { SnackBarE2EComponent } from '../../../components/ui/snackbar.e2e.component'; import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component'; import { WiedervorlageInVorgangE2EComponent } from '../../../components/wiedervorlage/wiedervorlage-in-vorgang.e2e.component'; import { WiedervorlageE2EComponent } from '../../../components/wiedervorlage/wiedervorlage-page.e2e.component'; +import { BinaryFileSnackbarMessageE2E } from '../../../model/binary-file'; import { VorgangE2E } from '../../../model/vorgang'; import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; import { VorgangPage } from '../../../page-objects/vorgang.po'; import { WiedervorlagePage } from '../../../page-objects/wiedervorlage.po'; -import { - containClass, - contains, - exist, - haveLength, - notContainClass, - notExist, -} from '../../../support/cypress.util'; -import { - TEST_FILE_WITHOUT_CONTENT, - TEST_FILE_WITH_CONTENT, - TEST_FILE_WITH_CONTENT_46MB, -} from '../../../support/data.util'; -import { uploadEmptyFile, uploadFile } from '../../../support/file-upload'; +import { containClass, contains, exist, haveLength, notContainClass, notExist } from '../../../support/cypress.util'; +import { TEST_FILE_WITH_CONTENT, TEST_FILE_WITH_CONTENT_46MB, TEST_FILE_WITHOUT_CONTENT } from '../../../support/data.util'; import { loginAsSabine } from '../../../support/user-util'; import { createVorgang, initVorgang, objectIds } from '../../../support/vorgang-util'; -import { - createWiedervorlageAttachedItem, - createWiedervorlageItem, -} from '../../../support/wiedervorlage-util'; +import { createWiedervorlageAttachedItem, createWiedervorlageItem } from '../../../support/wiedervorlage-util'; describe('Wiedervorlage attachments', () => { const mainPage: MainPage = new MainPage(); @@ -74,16 +57,16 @@ describe('Wiedervorlage attachments', () => { const wiedervorlageContainerInVorgang = vorgangPage.getWiedervorlagenContainer(); const wiedervorlagePage: WiedervorlagePage = new WiedervorlagePage(); - const wiedervorlageContainer: WiedervorlageE2EComponent = - wiedervorlagePage.getWiedervorlageContainer(); - const attachmentContainer: AttachmentContainerE2EComponent = - wiedervorlageContainer.getAttachmentContainer(); + const wiedervorlageContainer: WiedervorlageE2EComponent = wiedervorlagePage.getWiedervorlageContainer(); + const attachmentContainer: AttachmentContainerE2EComponent = wiedervorlageContainer.getAttachmentContainer(); const attachmentList: AttachmentListE2EComponent = attachmentContainer.getList(); - const subnavigation: WiedervorlageSubnavigationE2EComponent = - wiedervorlagePage.getSubnavigation(); + const subnavigation: WiedervorlageSubnavigationE2EComponent = wiedervorlagePage.getSubnavigation(); const snackbar: SnackBarE2EComponent = mainPage.getSnackBar(); + const wiedervorlageHelper: E2EWiedervorlageHelper = new E2EWiedervorlageHelper(); + const attachmentHelper: E2EAttachmentHelper = new E2EAttachmentHelper(); + const WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF: string = 'WiedervorlageWithAttachments'; const vorgang: VorgangE2E = createVorgang(); @@ -113,53 +96,41 @@ describe('Wiedervorlage attachments', () => { }); it('should show wiedervorlage page on click on create wiedervorlage button', () => { - wiedervorlageContainerInVorgang.getCreateWiedervorlageButton().click(); - waitForSpinnerToDisappear(); + wiedervorlageHelper.openNewWiedervorlage(); exist(wiedervorlagePage.getRoot()); }); it('should show empty attachment after upload', () => { - uploadEmptyFile(attachmentContainer.getUploadInput(), TEST_FILE_WITHOUT_CONTENT); - waitForSpinnerToDisappear(); + attachmentHelper.uploadEmptyAttachment(); exist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); }); it('should download empty attachment on click', () => { - attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getDownloadButton().click(); - waitForSpinnerToDisappear(); + attachmentHelper.downloadAttachment(TEST_FILE_WITHOUT_CONTENT); exist(readFileFromDownloads(TEST_FILE_WITHOUT_CONTENT)); }); it('should show attachment with content after download', () => { - uploadFile(attachmentContainer.getUploadInput(), TEST_FILE_WITH_CONTENT); - waitForSpinnerToDisappear(); + attachmentHelper.uploadAttachment(TEST_FILE_WITH_CONTENT); exist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); }); - it('should error snackbar after upload to large file', { defaultCommandTimeout: 30000 }, () => { - uploadFile(attachmentContainer.getUploadInput(), TEST_FILE_WITH_CONTENT_46MB); - waitForSpinnerToDisappear(); + it('should show failed upload on bigger size than allowed', { defaultCommandTimeout: 300000 }, () => { + attachmentHelper.uploadAttachment(TEST_FILE_WITH_CONTENT_46MB); - exist(snackbar.getMessage()); + exist(attachmentList.getLoadingOrErrorItem(TEST_FILE_WITH_CONTENT_46MB).getRoot()); contains( - snackbar.getMessage(), + attachmentList.getLoadingOrErrorItem(TEST_FILE_WITH_CONTENT_46MB).getRoot(), BinaryFileSnackbarMessageE2E.ATTACHMENT_NOT_ADDED.replace('{size}', '40MB'), ); }); - it('should close snackbar on close button', () => { - snackbar.getCloseButton().click(); - - notExist(snackbar.getMessage()); - }); - it('should download attachment on click', () => { - attachmentList.getItem(TEST_FILE_WITH_CONTENT).getDownloadButton().click(); - waitForSpinnerToDisappear(); + attachmentHelper.downloadAttachment(TEST_FILE_WITH_CONTENT); exist(readFileFromDownloads(TEST_FILE_WITH_CONTENT)); }); @@ -169,10 +140,7 @@ describe('Wiedervorlage attachments', () => { wiedervorlageContainer.getSpeichernButton().click(); exist(snackbar.getMessage()); - contains( - snackbar.getMessage(), - 'Die Wiedervorlage "' + WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF + '" wurde angelegt', - ); + contains(snackbar.getMessage(), 'Die Wiedervorlage "' + WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF + '" wurde angelegt'); exist(snackbar.getCloseButton()); }); @@ -183,9 +151,7 @@ describe('Wiedervorlage attachments', () => { }); it('should show attachments on wiedervorlage in vorgang-detail after save ', () => { - wiedervorlageContainerInVorgang - .getWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF) - .expandItem(); + wiedervorlageContainerInVorgang.getWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF).expandItem(); exist( wiedervorlageContainerInVorgang @@ -203,6 +169,14 @@ describe('Wiedervorlage attachments', () => { .getItem(TEST_FILE_WITH_CONTENT) .getRoot(), ); + notExist( + wiedervorlageContainerInVorgang + .getWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF) + .getAttachmentContainer() + .getList() + .getLoadingOrErrorItem(TEST_FILE_WITH_CONTENT_46MB) + .getRoot(), + ); }); it('should download empty attachment on click in list', () => { @@ -232,21 +206,63 @@ describe('Wiedervorlage attachments', () => { }); }); - describe('Delete attachment', () => { - const wiedervorlageComp: WiedervorlageInVorgangE2EComponent = - wiedervorlageContainerInVorgang.getWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF); + describe('Upload multiple attachments', () => { + const wiedervorlageComp: WiedervorlageInVorgangE2EComponent = wiedervorlageContainerInVorgang.getWiedervorlage( + WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF, + ); it('should open wiedervorlage page', () => { - wiedervorlageComp.getLink().click(); + wiedervorlageHelper.openWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF); + + exist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + exist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); + }); + + it('should delete all', () => { + attachmentHelper.deleteAttachment(TEST_FILE_WITH_CONTENT); + attachmentHelper.deleteAttachment(TEST_FILE_WITHOUT_CONTENT); + + notExist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); + notExist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + }); + + it('should do upload', () => { + attachmentHelper.uploadAttachments([TEST_FILE_WITHOUT_CONTENT, TEST_FILE_WITH_CONTENT]); + + exist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + exist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); + + wiedervorlageContainer.getSpeichernButton().click(); + exist(snackbar.getMessage()); + contains(snackbar.getMessage(), `Die Wiedervorlage "${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF}" wurde gespeichert`); + snackbar.getCloseButton().click(); + }); + + it('check attachments in wiedervorlage list', () => { waitForSpinnerToDisappear(); + wiedervorlageComp.getExpandButton(); + wiedervorlageComp.expandItem(); + + exist(wiedervorlageComp.getAttachmentContainer().getList().getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + exist(wiedervorlageComp.getAttachmentContainer().getList().getItem(TEST_FILE_WITH_CONTENT).getRoot()); + }); + }); + + describe('Delete attachment', () => { + const wiedervorlageComp: WiedervorlageInVorgangE2EComponent = wiedervorlageContainerInVorgang.getWiedervorlage( + WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF, + ); + + it('should open wiedervorlage page', () => { + wiedervorlageHelper.openWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF); + exist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); exist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); }); it('delete', () => { - attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getDeleteButton().click(); - waitForSpinnerToDisappear(); + attachmentHelper.deleteAttachment(TEST_FILE_WITHOUT_CONTENT); notExist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); exist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); @@ -256,74 +272,44 @@ describe('Wiedervorlage attachments', () => { wiedervorlageContainer.getSpeichernButton().click(); exist(snackbar.getMessage()); - contains( - snackbar.getMessage(), - `Die Wiedervorlage "${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF}" wurde gespeichert`, - ); + contains(snackbar.getMessage(), `Die Wiedervorlage "${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF}" wurde gespeichert`); snackbar.getCloseButton().click(); }); - it('(Skip reason in OZG-4658) check attachments in wiedervorlage list', () => { + it('check attachments in wiedervorlage list', () => { waitForSpinnerToDisappear(); wiedervorlageComp.getExpandButton(); wiedervorlageComp.expandItem(); + waitForSpinnerToDisappear(); - notExist( - wiedervorlageComp - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_WITHOUT_CONTENT) - .getRoot(), - ); - exist( - wiedervorlageComp - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_WITH_CONTENT) - .getRoot(), - ); + notExist(wiedervorlageComp.getAttachmentContainer().getList().getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + exist(wiedervorlageComp.getAttachmentContainer().getList().getItem(TEST_FILE_WITH_CONTENT).getRoot()); }); }); describe('switch to wiedervorlage without attachments', () => { - const wiedervorlageComp: WiedervorlageInVorgangE2EComponent = - wiedervorlageContainerInVorgang.getWiedervorlage(wiedervorlage.betreff); + const wiedervorlageComp: WiedervorlageInVorgangE2EComponent = wiedervorlageContainerInVorgang.getWiedervorlage( + wiedervorlage.betreff, + ); it('should not show any attachments', () => { - wiedervorlageComp.getLink().click(); - waitForSpinnerToDisappear(); + wiedervorlageHelper.openWiedervorlage(wiedervorlage.betreff); - notExist( - wiedervorlageComp - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_WITHOUT_CONTENT) - .getRoot(), - ); - notExist( - wiedervorlageComp - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_WITH_CONTENT) - .getRoot(), - ); + notExist(wiedervorlageComp.getAttachmentContainer().getList().getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + notExist(wiedervorlageComp.getAttachmentContainer().getList().getItem(TEST_FILE_WITH_CONTENT).getRoot()); wiedervorlagePage.getSubnavigation().navigateBack(); + waitForSpinnerToDisappear(); }); }); describe('Same number of attachments after status change', () => { - const wiedervorlageComp: WiedervorlageInVorgangE2EComponent = - wiedervorlageContainerInVorgang.getWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF); - it('should open wiedervorlage page', () => { - wait(500); - wiedervorlageComp.getLink().click(); - - waitForSpinnerToDisappear(); + wiedervorlageHelper.openWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF); exist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); + notExist(attachmentList.getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); }); it('should mark as erledigt', () => { @@ -332,10 +318,7 @@ describe('Wiedervorlage attachments', () => { waitForSpinnerToDisappear(); containClass(wiedervorlageContainer.getStatusDot(), 'erledigt'); - contains( - snackBar.getMessage(), - `Die Wiedervorlage ${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF} wurde erledigt`, - ); + contains(snackBar.getMessage(), `Die Wiedervorlage ${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF} wurde erledigt`); }); it('should close snackBar on close', () => { @@ -350,10 +333,7 @@ describe('Wiedervorlage attachments', () => { waitForSpinnerToDisappear(); notContainClass(wiedervorlageContainer.getStatusDot(), 'erledigt'); - contains( - snackBar.getMessage(), - `Die Wiedervorlage ${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF} wurde wiedereröffnet`, - ); + contains(snackBar.getMessage(), `Die Wiedervorlage ${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF} wurde wiedereröffnet`); }); it('should close snackBar on close', () => { @@ -374,8 +354,7 @@ describe('Wiedervorlage attachments', () => { describe('delete last attachment', () => { it('should remove on delete', () => { - attachmentList.getItem(TEST_FILE_WITH_CONTENT).getDeleteButton().click(); - waitForSpinnerToDisappear(); + attachmentHelper.deleteAttachment(TEST_FILE_WITH_CONTENT); notExist(attachmentList.getItem(TEST_FILE_WITH_CONTENT).getRoot()); }); @@ -385,10 +364,7 @@ describe('Wiedervorlage attachments', () => { waitForSpinnerToDisappear(); exist(snackbar.getMessage()); - contains( - snackbar.getMessage(), - `Die Wiedervorlage "${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF}" wurde gespeichert`, - ); + contains(snackbar.getMessage(), `Die Wiedervorlage "${WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF}" wurde gespeichert`); }); it('should close snackBar on close', () => { @@ -397,27 +373,16 @@ describe('Wiedervorlage attachments', () => { notExist(snackBar.getMessage()); }); - it('(Skip reason in OZG-4658) should have no attachments after save', () => { - const wiedervorlageComp: WiedervorlageInVorgangE2EComponent = - wiedervorlageContainerInVorgang.getWiedervorlage(WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF); + it('should have no attachments after save', () => { + const wiedervorlageComp: WiedervorlageInVorgangE2EComponent = wiedervorlageContainerInVorgang.getWiedervorlage( + WIEDERVORLAGE_WITH_ATTACHMENTS_BETREFF, + ); wiedervorlageComp.getExpandButton(); wiedervorlageComp.expandItem(); - notExist( - wiedervorlageComp - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_WITHOUT_CONTENT) - .getRoot(), - ); - notExist( - wiedervorlageComp - .getAttachmentContainer() - .getList() - .getItem(TEST_FILE_WITH_CONTENT) - .getRoot(), - ); + notExist(wiedervorlageComp.getAttachmentContainer().getList().getItem(TEST_FILE_WITHOUT_CONTENT).getRoot()); + notExist(wiedervorlageComp.getAttachmentContainer().getList().getItem(TEST_FILE_WITH_CONTENT).getRoot()); }); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.executor.ts b/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.executor.ts new file mode 100644 index 0000000000000000000000000000000000000000..25b0dce789373955a60b97632848ac2c8c218dde --- /dev/null +++ b/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.executor.ts @@ -0,0 +1,44 @@ +import { AttachmentContainerE2EComponent, AttachmentE2EItem } from '../../components/attachment/attachment.e2e.component'; +import { waitForSpinnerToDisappear } from '../../page-objects/main.po'; +import { exist, notExist } from '../../support/cypress.util'; +import { uploadEmptyFile, uploadFiles } from '../../support/file-upload'; + +export class E2EAttachmentExecutor { + private readonly attachmentContainer: AttachmentContainerE2EComponent = new AttachmentContainerE2EComponent(); + + public uploadAttachment(fileNames: string[]): void { + uploadFiles(this.getUploadInput(), fileNames); + waitForSpinnerToDisappear(); + } + + public uploadEmptyAttachment(fileName: string): void { + uploadEmptyFile(this.getUploadInput(), fileName); + waitForSpinnerToDisappear(); + } + + private getUploadInput(): Cypress.Chainable<HTMLElement> { + return this.attachmentContainer.getUploadInput(); + } + + public deleteAttachments(fileNames: string[]): void { + fileNames.forEach((fileName: string) => this.deleteAttachment(fileName)); + } + + private deleteAttachment(fileName: string): void { + const attachment: AttachmentE2EItem = this.getAttachment(fileName); + exist(attachment.getRoot()); + attachment.getDeleteButton().click(); + notExist(attachment.getRoot()); + } + + public downloadAttachment(fileName: string): void { + const attachment: AttachmentE2EItem = this.getAttachment(fileName); + exist(attachment.getRoot()); + attachment.getDownloadButton().click(); + waitForSpinnerToDisappear(); + } + + private getAttachment(fileName: string): AttachmentE2EItem { + return this.attachmentContainer.getList().getItem(fileName); + } +} diff --git a/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.helper.ts b/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd9743081a8eae3596b692daf53ff5b2479ce74b --- /dev/null +++ b/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.helper.ts @@ -0,0 +1,30 @@ +import { TEST_FILE_WITH_CONTENT, TEST_FILE_WITHOUT_CONTENT } from '../../support/data.util'; +import { E2EAttachmentExecutor } from './attachment.executor'; + +export class E2EAttachmentHelper { + private readonly executor: E2EAttachmentExecutor = new E2EAttachmentExecutor(); + + public deleteAttachment(fileName: string): void { + this.deleteAttachments([fileName]); + } + + public deleteAttachments(fileNames: string[]): void { + this.executor.deleteAttachments(fileNames); + } + + public uploadAttachment(fileName: string = TEST_FILE_WITH_CONTENT): void { + this.uploadAttachments([fileName]); + } + + public uploadAttachments(fileNames: string[]): void { + this.executor.uploadAttachment(fileNames); + } + + public uploadEmptyAttachment(fileName: string = TEST_FILE_WITHOUT_CONTENT): void { + this.executor.uploadEmptyAttachment(fileName); + } + + public downloadAttachment(fileName: string): void { + this.executor.downloadAttachment(fileName); + } +} diff --git a/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.verifier.ts b/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.verifier.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c68292a0e10bdfac3e20d83377120a258d08b25 --- /dev/null +++ b/alfa-client/apps/alfa-e2e/src/helper/attachment/attachment.verifier.ts @@ -0,0 +1,23 @@ +import { AttachmentContainerE2EComponent } from '../../components/attachment/attachment.e2e.component'; +import { readFileFromDownloads } from '../../support/cypress-helper'; +import { exist, notExist } from '../../support/cypress.util'; + +export class E2EAttachmentVerifier { + private readonly attachmentContainer: AttachmentContainerE2EComponent = new AttachmentContainerE2EComponent(); + + public verifyAttachmentsInList(filesNames: string[]): void { + filesNames.forEach((filesName: string) => this.verifyAttachmentInList(filesName)); + } + + public verifyAttachmentInList(filesName: string): void { + exist(this.attachmentContainer.getList().getItem(filesName).getRoot()); + } + + public verifyAttachmentNotInList(filesName: string): void { + notExist(this.attachmentContainer.getList().getItem(filesName).getRoot()); + } + + public verifyAttachmentDownload(fileName: string): void { + exist(readFileFromDownloads(fileName)); + } +} diff --git a/alfa-client/apps/alfa-e2e/src/helper/kommentar/kommentar.executor.ts b/alfa-client/apps/alfa-e2e/src/helper/kommentar/kommentar.executor.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/alfa-client/apps/alfa-e2e/src/helper/wiedervorlage/wiedervorlage.helper.ts b/alfa-client/apps/alfa-e2e/src/helper/wiedervorlage/wiedervorlage.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c6c96e50c79a44ee3d6a771d16e5a74fb3bd1a5 --- /dev/null +++ b/alfa-client/apps/alfa-e2e/src/helper/wiedervorlage/wiedervorlage.helper.ts @@ -0,0 +1,13 @@ +import { E2EWiedervorlageNavigator } from './wiedervorlage.navigator'; + +export class E2EWiedervorlageHelper { + private navigator: E2EWiedervorlageNavigator = new E2EWiedervorlageNavigator(); + + public openWiedervorlage(betreff: string): void { + this.navigator.openWiedervorlage(betreff); + } + + public openNewWiedervorlage(): void { + this.navigator.openNewWiedervorlage(); + } +} diff --git a/alfa-client/apps/alfa-e2e/src/helper/wiedervorlage/wiedervorlage.navigator.ts b/alfa-client/apps/alfa-e2e/src/helper/wiedervorlage/wiedervorlage.navigator.ts new file mode 100644 index 0000000000000000000000000000000000000000..42fe2c26a02ce0b3518c7bcd0f39aabaf9a25edd --- /dev/null +++ b/alfa-client/apps/alfa-e2e/src/helper/wiedervorlage/wiedervorlage.navigator.ts @@ -0,0 +1,26 @@ +import { WiedervorlageInVorgangE2EComponent } from '../../components/wiedervorlage/wiedervorlage-in-vorgang.e2e.component'; +import { WiedervorlagenInVorgangE2EComponent } from '../../components/wiedervorlage/wiedervorlagen-in-vorgang.e2e.component'; +import { VorgangPage } from '../../page-objects/vorgang.po'; +import { WiedervorlagePage } from '../../page-objects/wiedervorlage.po'; +import { exist } from '../../support/cypress.util'; + +export class E2EWiedervorlageNavigator { + private vorgangPage: VorgangPage = new VorgangPage(); + private wiedervorlagenInVorgang: WiedervorlagenInVorgangE2EComponent = this.vorgangPage.getWiedervorlagenContainer(); + + private wiedervorlagePage: WiedervorlagePage = new WiedervorlagePage(); + + public openWiedervorlage(betreff: string): void { + const wiedervorlageInVorgang: WiedervorlageInVorgangE2EComponent = this.wiedervorlagenInVorgang.getWiedervorlage(betreff); + exist(wiedervorlageInVorgang.getRoot()); + exist(wiedervorlageInVorgang.getLink()); + wiedervorlageInVorgang.getLink().click(); + exist(this.wiedervorlagePage.getHeadline()); + } + + public openNewWiedervorlage(): void { + exist(this.wiedervorlagenInVorgang.getCreateWiedervorlageButton()); + this.wiedervorlagenInVorgang.getCreateWiedervorlageButton().click(); + exist(this.wiedervorlagePage.getHeadline()); + } +} diff --git a/alfa-client/apps/alfa-e2e/src/page-objects/vorgang.po.ts b/alfa-client/apps/alfa-e2e/src/page-objects/vorgang.po.ts index caa0e2872c6ffb9820602075dd194c072b8b6008..6a1d1b397b3e0a9eb91362cd0220230b67a5db14 100644 --- a/alfa-client/apps/alfa-e2e/src/page-objects/vorgang.po.ts +++ b/alfa-client/apps/alfa-e2e/src/page-objects/vorgang.po.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { AttachmentContainerE2EComponent } from '../components/attachment/attachment.e2e.component'; -import { KommentareInVorgangE2EComponent } from '../components/kommentar/kommentar-list.e2e.component'; +import { KommentarListInVorgangE2EComponent } from '../components/kommentar/kommentar-list.e2e.component'; import { PostfachMailFormularE2EComponent } from '../components/postfach/postfach-mail-formular.e2e.component'; import { PostfachMailE2EComponent } from '../components/postfach/postfach-mail.e2e.component'; import { FixedDialogE2EComponent } from '../components/ui/fixed-dialog.e2e.component'; @@ -40,42 +40,29 @@ import { VorgangZusammenarbeitE2EComponent } from '../components/vorgang/vorgang import { WiedervorlagenInVorgangE2EComponent } from '../components/wiedervorlage/wiedervorlagen-in-vorgang.e2e.component'; export class VorgangPage { - private readonly subnavigation: VorgangSubnavigationE2EComponent = - new VorgangSubnavigationE2EComponent(); - private readonly vorgangDetailHeader: VorgangDetailHeaderE2EComponent = - new VorgangDetailHeaderE2EComponent(); - private readonly formularDatenContainer: VorgangFormularDatenE2EComponent = - new VorgangFormularDatenE2EComponent(); - private readonly formularButtons: VorgangFormularButtonsE2EComponent = - new VorgangFormularButtonsE2EComponent(); + private readonly subnavigation: VorgangSubnavigationE2EComponent = new VorgangSubnavigationE2EComponent(); + private readonly vorgangDetailHeader: VorgangDetailHeaderE2EComponent = new VorgangDetailHeaderE2EComponent(); + private readonly formularDatenContainer: VorgangFormularDatenE2EComponent = new VorgangFormularDatenE2EComponent(); + private readonly formularButtons: VorgangFormularButtonsE2EComponent = new VorgangFormularButtonsE2EComponent(); private readonly moreMenu: VorgangMoreMenuE2EComponent = new VorgangMoreMenuE2EComponent(); - private readonly aktenzeichenEditor: VorgangAktenzeichenEditE2EComponent = - new VorgangAktenzeichenEditE2EComponent(); - private readonly bescheidWizard: VorgangBescheidWizardE2EComponent = - new VorgangBescheidWizardE2EComponent(); + private readonly aktenzeichenEditor: VorgangAktenzeichenEditE2EComponent = new VorgangAktenzeichenEditE2EComponent(); + private readonly bescheidWizard: VorgangBescheidWizardE2EComponent = new VorgangBescheidWizardE2EComponent(); private readonly bescheide: VorgangBescheideE2EComponent = new VorgangBescheideE2EComponent(); - private readonly wiedervorlagen: WiedervorlagenInVorgangE2EComponent = - new WiedervorlagenInVorgangE2EComponent(); - private readonly forwardingContainer: VorgangForwardingE2EComponent = - new VorgangForwardingE2EComponent(); - private readonly attachmentContainer: AttachmentContainerE2EComponent = - new AttachmentContainerE2EComponent(); - private readonly kommentarContainer: KommentareInVorgangE2EComponent = - new KommentareInVorgangE2EComponent(); + private readonly wiedervorlagen: WiedervorlagenInVorgangE2EComponent = new WiedervorlagenInVorgangE2EComponent(); + private readonly forwardingContainer: VorgangForwardingE2EComponent = new VorgangForwardingE2EComponent(); + private readonly attachmentContainer: AttachmentContainerE2EComponent = new AttachmentContainerE2EComponent(); + private readonly kommentarContainer: KommentarListInVorgangE2EComponent = new KommentarListInVorgangE2EComponent(); private readonly postfachMailContainer: PostfachMailE2EComponent = new PostfachMailE2EComponent(); - private readonly antragstellerContainer: AntragstellerE2EComponent = - new AntragstellerE2EComponent(); - private readonly zusammenArbeitContainer: VorgangZusammenarbeitE2EComponent = - new VorgangZusammenarbeitE2EComponent(); + private readonly antragstellerContainer: AntragstellerE2EComponent = new AntragstellerE2EComponent(); + private readonly zusammenArbeitContainer: VorgangZusammenarbeitE2EComponent = new VorgangZusammenarbeitE2EComponent(); private readonly fixedDialog: FixedDialogE2EComponent = new FixedDialogE2EComponent(); - private readonly postfachMailFormular: PostfachMailFormularE2EComponent = - new PostfachMailFormularE2EComponent(); + private readonly postfachMailFormular: PostfachMailFormularE2EComponent = new PostfachMailFormularE2EComponent(); private readonly locatorSpinner: string = 'spinner'; private readonly locatorProgressBar: string = 'progress-bar'; - public getKommentarContainer(): KommentareInVorgangE2EComponent { + public getKommentarContainer(): KommentarListInVorgangE2EComponent { return this.kommentarContainer; } diff --git a/alfa-client/apps/alfa-e2e/src/page-objects/wiedervorlage.po.ts b/alfa-client/apps/alfa-e2e/src/page-objects/wiedervorlage.po.ts index ce5c00e85ce61eb22109881bf26ab4bf3f121b02..cf253b1d3e54ade65ed5711a9b689e88b2640baf 100644 --- a/alfa-client/apps/alfa-e2e/src/page-objects/wiedervorlage.po.ts +++ b/alfa-client/apps/alfa-e2e/src/page-objects/wiedervorlage.po.ts @@ -25,20 +25,24 @@ import { WiedervorlageE2EComponent } from '../components/wiedervorlage/wiedervor import { WiedervorlageSubnavigationE2EComponent } from '../components/wiedervorlage/wiedervorlage-subnavigation'; export class WiedervorlagePage { - private readonly subnavigation: WiedervorlageSubnavigationE2EComponent = - new WiedervorlageSubnavigationE2EComponent(); - private readonly wiedervorlageContainer: WiedervorlageE2EComponent = - new WiedervorlageE2EComponent(); - private readonly locatorWiedervorlagePage: string = 'wiedervorlage-page'; + private readonly root: string = 'wiedervorlage-page'; - public getRoot() { - return cy.getTestElement(this.locatorWiedervorlagePage); + private readonly subnavigation: WiedervorlageSubnavigationE2EComponent = new WiedervorlageSubnavigationE2EComponent(); + private readonly headline: string = 'wiedervorlage-headline'; + private readonly wiedervorlageContainer: WiedervorlageE2EComponent = new WiedervorlageE2EComponent(); + + public getRoot(): Cypress.Chainable<Element> { + return cy.getTestElement(this.root); } public getSubnavigation(): WiedervorlageSubnavigationE2EComponent { return this.subnavigation; } + public getHeadline(): Cypress.Chainable<Element> { + return cy.getTestElement(this.headline); + } + public getWiedervorlageContainer(): WiedervorlageE2EComponent { return this.wiedervorlageContainer; } diff --git a/alfa-client/apps/alfa-e2e/src/support/commands.ts b/alfa-client/apps/alfa-e2e/src/support/commands.ts index 8873b6afe8672819cf9ea75cf441955fabb0f004..30e88fd0f58f79fb5d3db717bb46a835fc2b5b54 100644 --- a/alfa-client/apps/alfa-e2e/src/support/commands.ts +++ b/alfa-client/apps/alfa-e2e/src/support/commands.ts @@ -73,6 +73,7 @@ declare namespace Cypress { interface Chainable<Subject> { getTestElementWithOid(oid: string, ...args); getTestElement(selector: string, ...args); + getTestElementContaining(selector: string, ...args); getTestElementWithClass(selector: string, ...args); findTestElementWithClass(selector: string, ...args); findElement(selector: string); @@ -90,6 +91,10 @@ Cypress.Commands.add('getTestElement', (selector, ...args) => { return cy.get(`[${DATA_TEST_ID}~="${selector}"]`, ...args); }); +Cypress.Commands.add('getTestElementContaining', (selector, ...args) => { + return cy.get(`[${DATA_TEST_ID}*="${selector}"]`, ...args); +}); + Cypress.Commands.add('getTestElementWithClass', (selector, ...args) => { console.log( 'Achtung: Potentiell nicht eindeutiges Ergebnis, weil eine data-test-class mit cy.get() von der DOM-Root aus gesucht wird.', @@ -101,13 +106,9 @@ Cypress.Commands.add('getTestElementWithOid', (oid, ...args) => { return cy.getTestElement(oid, ...args); }); -Cypress.Commands.add( - 'findTestElementWithClass', - { prevSubject: true }, - (subject: any, selector) => { - return subject.find(`[${DATA_TEST_CLASS}="${selector}"]`); - }, -); +Cypress.Commands.add('findTestElementWithClass', { prevSubject: true }, (subject: any, selector) => { + return subject.find(`[${DATA_TEST_CLASS}="${selector}"]`); +}); Cypress.Commands.add('findElement', { prevSubject: true }, (subject: any, selector: string) => { return subject.find(selector); diff --git a/alfa-client/apps/alfa-e2e/src/support/cypress-helper.ts b/alfa-client/apps/alfa-e2e/src/support/cypress-helper.ts index 9c2bd2f7944d04ee78e668cf89f1cbe9c396689f..b4f477e2422f3bd0ea391d7d3259c29bd4b2a8a6 100644 --- a/alfa-client/apps/alfa-e2e/src/support/cypress-helper.ts +++ b/alfa-client/apps/alfa-e2e/src/support/cypress-helper.ts @@ -134,11 +134,12 @@ export function initUsermanagerData(data: UsermanagerUserE2E[]): void { cy.task(CypressTasks.INIT_USERMANAGER_DATA, { collection: MongoCollections.USER, data }); } -export function dropCollections() { +export function dropCollections(): void { cy.task(CypressTasks.DROP_COLLECTIONS, [ MongoCollections.COMMAND, MongoCollections.VORGANG, MongoCollections.VORGANG_ATTACHED_ITEM, + MongoCollections.OZG_CLOUD_FILE, MongoCollections.FS_FILES, MongoCollections.FS_CHUNKS, ]); diff --git a/alfa-client/apps/alfa-e2e/src/support/data.util.ts b/alfa-client/apps/alfa-e2e/src/support/data.util.ts index 47f84cd7927ba964684b1de514a846d866d188af..46c335ab573be9cd33c24970ebd37cce56f1da15 100644 --- a/alfa-client/apps/alfa-e2e/src/support/data.util.ts +++ b/alfa-client/apps/alfa-e2e/src/support/data.util.ts @@ -36,10 +36,13 @@ export const ORGANISATIONSEINHEITEN_ID_FOR_ADELHEIT = '10363455'; export const ORGANISATIONSEINHEITEN_ID_FOR_SABINE = '9030229'; export const ORGANISATIONSEINHEITEN_ID_FOR_BEATE = '12345678'; +//TODO Rename = nicht fachlich bezogen bspw. TEST_FILE_WITH_CONTENT_5_MB anstelle von "invalid" oder "big" export const TEST_FILE_BESCHEID_VALID: string = 'Bescheid_valid.pdf'; export const TEST_FILE_BESCHEID_BIG: string = 'Bescheid_5mb.pdf'; export const TEST_FILE_BESCHEID_ANHANG_VALID: string = 'Anhang_valid.pdf'; export const TEST_FILE_BESCHEID_ANHANG_BIG: string = 'Anhang_5mb.pdf'; +// + export const TEST_FILE_JPG: string = 'small_jpg.jpg'; export const TEST_FILE_JPEG: string = 'small_jpeg.jpeg'; export const TEST_FILE_PNG: string = 'small_png.png'; diff --git a/alfa-client/apps/alfa-e2e/src/support/file-upload.ts b/alfa-client/apps/alfa-e2e/src/support/file-upload.ts index eb8b69908acb11159d4c8b35d1ee8471bc4b9637..d01cc9a98299f7766f316a5b84bda890e4c7e3ba 100644 --- a/alfa-client/apps/alfa-e2e/src/support/file-upload.ts +++ b/alfa-client/apps/alfa-e2e/src/support/file-upload.ts @@ -23,10 +23,14 @@ */ import 'cypress-file-upload'; -export function uploadFile(inputElement: any, fileName: string): void { - inputElement.attachFile(fileName); +export function uploadFile(inputElement: Cypress.Chainable<HTMLElement>, fileName: string): void { + uploadFiles(inputElement, [fileName]); } -export function uploadEmptyFile(inputElement: any, fileName: string): void { +export function uploadFiles(inputElement: Cypress.Chainable<HTMLElement>, fileNames: string[]): void { + inputElement.attachFile(fileNames); +} + +export function uploadEmptyFile(inputElement: Cypress.Chainable<HTMLElement>, fileName: string): void { inputElement.attachFile(fileName, { allowEmpty: true }); } diff --git a/alfa-client/apps/alfa/package.json b/alfa-client/apps/alfa/package.json index c5322d141c5350c1a5a192513595e4a25eb170f9..db8d098a8244e0cfde3ebcd7f8e148c07ffd9316 100644 --- a/alfa-client/apps/alfa/package.json +++ b/alfa-client/apps/alfa/package.json @@ -1,4 +1,4 @@ { "name": "alfa", - "version": "2.21.0-SNAPSHOT" + "version": "2.22.0-SNAPSHOT" } diff --git a/alfa-client/apps/demo/src/app/app.component.html b/alfa-client/apps/demo/src/app/app.component.html index 226972864701eedbc6a7b16461f51f85130a88aa..22a2f09c8bd01b957ed678fd264ac675685cf402 100644 --- a/alfa-client/apps/demo/src/app/app.component.html +++ b/alfa-client/apps/demo/src/app/app.component.html @@ -56,6 +56,70 @@ <ods-button destructive="true" variant="ghost" text="Organisationseinheit hinzufügen" /> </div> + <div class="my-12"> + <h1 class="mb-6 text-2xl font-semibold text-text">Auswertungen</h1> + <ods-button text="Auswertung hinzufügen" /> + <ul class="mt-6 divide-y divide-gray-300 rounded-md bg-background-50 text-text shadow-sm ring-1 ring-gray-300"> + <li> + <a + href="#" + class="flex flex-col items-start justify-between gap-2 rounded-t-md border-primary-600/50 px-6 py-4 hover:bg-background-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus lg:flex-row lg:gap-6" + > + <div class="flex-1"> + <div class="flex flex-wrap items-center gap-x-3"> + <h3 class="text-md font-semibold">Titel der Auswertung</h3> + </div> + </div> + + <dl class="flex-1"> + <dt class="sr-only">Formengine:</dt> + <dd>Formsolutions</dd> + <dt class="sr-only">FormID:</dt> + <dd>waffen/kleinerWaffenscheinKOPutopia</dd> + </dl> + </a> + </li> + <li> + <a + href="#" + class="flex flex-col items-start justify-between gap-2 rounded-t-md border-primary-600/50 px-6 py-4 hover:bg-background-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus lg:flex-row lg:gap-6" + > + <div class="flex-1"> + <div class="flex flex-wrap items-center gap-x-3"> + <h3 class="text-md font-semibold">Auswertungstitel 2</h3> + </div> + </div> + + <dl class="flex-1"> + <dt class="sr-only">Formengine:</dt> + <dd>AFM / cit/Intelliform</dd> + <dt class="sr-only">FormID:</dt> + <dd>eGewerbe/eGewerbeAnmeldung</dd> + </dl> + </a> + </li> + <li> + <a + href="#" + class="flex flex-col items-start justify-between gap-2 rounded-t-md border-primary-600/50 px-6 py-4 hover:bg-background-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus lg:flex-row lg:gap-6" + > + <div class="flex-1"> + <div class="flex flex-wrap items-center gap-x-3"> + <h3 class="text-md font-semibold">Titel 3</h3> + </div> + </div> + + <dl class="flex-1"> + <dt class="sr-only">Formengine:</dt> + <dd>dFördermittelantrag</dd> + <dt class="sr-only">FormID:</dt> + <dd>waffen/kleinerWaffenschein</dd> + </dl> + </a> + </li> + </ul> + </div> + <div class="my-12"> <h1 class="mb-6 text-2xl font-semibold text-text">Organisationseinheiten</h1> <ods-button text="Organisationseinheit hinzufügen" /> diff --git a/alfa-client/apps/info/package.json b/alfa-client/apps/info/package.json index 5861518686029b9621a5e39e361c49ce6f462690..f7b9ea61038da3ea7801ac1121b57c21816c49a6 100644 --- a/alfa-client/apps/info/package.json +++ b/alfa-client/apps/info/package.json @@ -1,4 +1,4 @@ { "name": "info", - "version": "1.6.0-SNAPSHOT" + "version": "1.7.0-SNAPSHOT" } diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.spec.ts index be3f115f38c800b3b1cf87b3aaec5044e9b3c121..348a6c4b49125dca4d13765528be6b83f8b6e1de 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.spec.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.spec.ts @@ -1,18 +1,18 @@ +import { mockWindowError } from '@alfa-client/test-utils'; import { FormControl, FormGroup } from '@angular/forms'; import { patchForm } from './form.util'; describe('FormUtil', () => { describe('patch form', () => { it('should not throw any errors', () => { - const errorHandler = jest.fn(); - window.onerror = errorHandler; + const errorSpy: jest.SpyInstance = mockWindowError(); const formGroup: FormGroup = new FormGroup({ existingKey: new FormControl(null), }); patchForm({ missingKey: 'dummyValue' }, formGroup); - expect(errorHandler).not.toHaveBeenCalled(); + expect(errorSpy).not.toHaveBeenCalled(); }); describe('on strings', () => { @@ -57,6 +57,15 @@ describe('FormUtil', () => { expect(arrayFormGroup.controls['arrayValue1'].value).toBeTruthy(); expect(arrayFormGroup.controls['arrayValue2'].value).toBeFalsy(); }); + + it('should not throw any error if control is missing', () => { + const errorSpy: jest.SpyInstance = mockWindowError(); + const formGroup: FormGroup = new FormGroup({ array: new FormGroup({}) }); + + patchForm({ array: ['arrayValue1'] }, formGroup); + + expect(errorSpy).not.toHaveBeenCalled(); + }); }); describe('on object value', () => { diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.ts index 877cf9e458b14b0d2f7732086f5e033ba3b40bf5..d57cf619353a41d05d4e9d866ca098331d4345b1 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/form.util.ts @@ -15,7 +15,9 @@ function patchNonStringValues(valueToPatch: any, formGroup: FormGroup): void { function patchNonStringValue(value: any, formGroup: FormGroup): void { if (Array.isArray(value)) { - value.forEach((oneValue: any) => formGroup.controls[oneValue].patchValue(true)); + value.forEach((oneValue: any) => { + if (formGroup.contains(oneValue)) formGroup.controls[oneValue].patchValue(true); + }); } else if (!isString(value) && !isBoolean(value)) { patchNonStringValues(value, formGroup); } diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.spec.ts index 865f904f83dbfd0ad22873ec8578531815b1c8dd..6fe0a332ba918b071d6bddfb712e7b4e177a4891 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.spec.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.spec.ts @@ -26,6 +26,7 @@ import { Injectable } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, UrlSegment } from '@angular/router'; +import { faker } from '@faker-js/faker/.'; import { createDummy, Dummy } from 'libs/tech-shared/test/dummy'; import { singleCold, singleHot } from 'libs/tech-shared/test/marbles'; import { createSpy, mock, Mock } from 'libs/test-utils/src/lib/mocking'; @@ -261,6 +262,19 @@ describe('KeycloakFormService', () => { expect(isInvalid).toBeTruthy(); }); }); + + describe('get id', () => { + const patchConfigId: string = faker.string.alphanumeric(); + const patchConfig: PatchConfig = { id: patchConfigId, doPatch: false }; + + it('should return id from patch config', () => { + service._patchConfig = patchConfig; + + const id: string = service.getId(); + + expect(id).toBe(patchConfigId); + }); + }); }); @Injectable() @@ -268,6 +282,7 @@ export class TestKeycloakFormService extends KeycloakFormService<Dummy> { public static readonly FIELD: string = 'attribute'; public static SUBMIT_OBSERVABLE = () => of(createEmptyStateResource()); + public static DELETE_OBSERVABLE = () => of(createEmptyStateResource()); public static LOAD_OBSERVABLE = () => of(createEmptyStateResource()); _initForm(): FormGroup { @@ -287,4 +302,8 @@ export class TestKeycloakFormService extends KeycloakFormService<Dummy> { _doSubmit(): Observable<StateResource<Dummy>> { return TestKeycloakFormService.SUBMIT_OBSERVABLE(); } + + _doDelete(): Observable<StateResource<Dummy>> { + return TestKeycloakFormService.DELETE_OBSERVABLE(); + } } diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts index ecd71702a2a6d3e6176dc5890bdc9cd61dc7fe7c..49a2e6c84e3fdbf69b22ca324ee0100a34ae6763 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts @@ -94,6 +94,10 @@ export abstract class KeycloakFormService<T> { public isInvalid(): boolean { return this.form.invalid; } + + public getId(): string { + return this._patchConfig.id; + } } export interface PatchConfig { diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts index ea3ca2d74ffe1eb2b6fb903a300d5b1b7b36e455..5a91a0813ffbe8dec5a89c9f526382168481bdcd 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts @@ -49,15 +49,16 @@ describe('KeycloakResourceService', () => { }); describe('getAll', () => { + const stateResource: StateResource<unknown> = createStateResource([]); + beforeEach(() => { - service.handleChanges = jest.fn(); + service.handleChanges = jest.fn().mockReturnValue(singleCold(stateResource)); }); - it('should return stateResource as observable', (done) => { - service.getAll().subscribe((stateResource) => { - expect(stateResource).toBe(service.stateResource.value); - done(); - }); + it('should return stateResource', () => { + const stateResource$: Observable<StateResource<unknown[]>> = service.getAll(); + + expect(stateResource$).toBeObservable(singleCold(stateResource)); }); it('should call handleChanges ', fakeAsync(() => { @@ -68,12 +69,25 @@ describe('KeycloakResourceService', () => { }); describe('handleChanges', () => { - it('should call doIfLoadingRequired', () => { - const doIfLoadingRequired: jest.SpyInstance<boolean> = jest.spyOn(ResourceUtil, 'doIfLoadingRequired'); + let doIfLoadingRequiredSpy: jest.SpyInstance<boolean>; + + beforeEach(() => { + doIfLoadingRequiredSpy = jest.spyOn(ResourceUtil, 'doIfLoadingRequired').mockImplementation(); + }); + it('should call doIfLoadingRequired', () => { service.handleChanges(emptyStateResource); - expect(doIfLoadingRequired).toHaveBeenCalled(); + expect(doIfLoadingRequiredSpy).toHaveBeenCalled(); + }); + + it('should return stateResource', (done) => { + service.stateResource.next(createStateResource([])); + + service.handleChanges(emptyStateResource).subscribe((stateResource: StateResource<[]>) => { + expect(stateResource).toEqual(createStateResource([])); + done(); + }); }); }); diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts index 572761923b93f3d3a21719659416b268fb17076f..ae74c73ee2c59c989a0db77dbaa1b881c4061448 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts @@ -22,22 +22,20 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { createEmptyStateResource, createStateResource, doIfLoadingRequired, StateResource } from '@alfa-client/tech-shared'; -import { BehaviorSubject, first, map, Observable, startWith, tap } from 'rxjs'; +import { BehaviorSubject, first, map, Observable, startWith, switchMap, tap } from 'rxjs'; export abstract class KeycloakResourceService<T> { - readonly stateResource: BehaviorSubject<StateResource<T[]>> = new BehaviorSubject({ - ...createStateResource<T[]>([]), - loaded: false, - }); + readonly stateResource: BehaviorSubject<StateResource<T[]>> = new BehaviorSubject(createEmptyStateResource()); public getAll(): Observable<StateResource<T[]>> { return this.stateResource .asObservable() - .pipe(tap((stateResource: StateResource<T[]>): void => this.handleChanges(stateResource))); + .pipe(switchMap((stateResource: StateResource<T[]>) => this.handleChanges(stateResource))); } - handleChanges(stateResource: StateResource<T[]>): void { + handleChanges(stateResource: StateResource<T[]>): Observable<StateResource<T[]>> { doIfLoadingRequired(stateResource, (): void => this.loadResource()); + return this.stateResource.asObservable(); } loadResource(): void { @@ -65,7 +63,7 @@ export abstract class KeycloakResourceService<T> { protected abstract _saveInKeycloak(item: T): Observable<T>; - public delete(id: string): Observable<StateResource<unknown>> { + public delete(id: string): Observable<StateResource<void>> { return this.handleLoading(this._deleteInKeycloak(id)); } diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.spec.ts index 43bc3d38565e870585b2ba846b8c2d681d977014..e86fbad2ab7cc0d9716dc13409990320dde904d7 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.spec.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.spec.ts @@ -1,6 +1,7 @@ import { AdminOrganisationsEinheit } from '@admin-client/organisations-einheit-shared'; import { mock, Mock } from '@alfa-client/test-utils'; import { TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker'; import KcAdminClient from '@keycloak/keycloak-admin-client'; import GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation'; import { createGroupRep } from '../../../organisations-einheit-shared/src/test/organisations-einheit'; @@ -85,4 +86,27 @@ describe('AdminOrganisationsEinheitRepository', () => { }); }); }); + + describe('delete', () => { + const organisationsEinheitId: string = faker.string.uuid(); + + beforeEach(() => { + kcAdminClient.groups = <any>{ + del: jest.fn().mockReturnValue(Promise.resolve()), + }; + }); + + it('should call kcAdminClient groups del', () => { + repository.delete(organisationsEinheitId); + + expect(kcAdminClient.groups['del']).toHaveBeenCalledWith({ id: organisationsEinheitId }); + }); + + it('should return void', (done) => { + repository.delete(organisationsEinheitId).subscribe((result: void) => { + expect(result).toBeUndefined(); + done(); + }); + }); + }); }); diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.ts index c2d454434e07562492f0c929bc710d7793a70403..abc3737d301f3f82b603a6bac0e5b85cd4e725ca 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/organisations-einheit.repository.ts @@ -43,4 +43,8 @@ export class AdminOrganisationsEinheitRepository { attributes: group.attributes, }; } + + public delete(organisationseinheitId: string): Observable<void> { + return from(this.kcAdminClient.groups.del({ id: organisationseinheitId })); + } } diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.spec.ts index ae9c56bedd57177e3bac765ec968d7d00660794d..afbb09bee049b4e79cda30ee0d97f3e4810799f9 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.spec.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.spec.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { User } from '@admin-client/user-shared'; +import { RoleMappings, User } from '@admin-client/user-shared'; import { UserRepository } from '@admin/keycloak-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { Mock, mock } from '@alfa-client/test-utils'; @@ -30,10 +30,10 @@ import { faker } from '@faker-js/faker'; import KcAdminClient from '@keycloak/keycloak-admin-client'; import GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation'; import MappingsRepresentation from '@keycloak/keycloak-admin-client/lib/defs/mappingsRepresentation'; -import RoleRepresentation, { RoleMappingPayload } from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation'; +import { RoleMappingPayload } from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation'; import { Users } from '@keycloak/keycloak-admin-client/lib/resources/users'; import { cold } from 'jest-marbles'; -import { omit } from 'lodash-es'; +import { omit, times } from 'lodash-es'; import { throwError } from 'rxjs'; import { createUser } from '../../../user-shared/test/user'; import { UserFormService } from '../../../user/src/lib/user-form/user.formservice'; @@ -71,18 +71,18 @@ describe('UserRepository', () => { expect(kcAdminClient.users['create']).toHaveBeenCalledWith(omit(user, 'groupIds')); }); - it('should call addUserRoles', fakeAsync(() => { - repository._addUserRoles = jest.fn(); + it('should call updateUserRoles', fakeAsync(() => { + repository._updateUserRoles = jest.fn(); repository.createInKeycloak(user).subscribe(); tick(); - expect(repository._addUserRoles).toHaveBeenCalledWith(user.id, user.clientRoles); + expect(repository._updateUserRoles).toHaveBeenCalledWith(user.id, user.clientRoles); })); it('should call sendActivationMail', (done) => { repository._sendActivationMail = jest.fn(); - repository._addUserRoles = jest.fn().mockReturnValue(Promise.resolve()); + repository._updateUserRoles = jest.fn().mockReturnValue(Promise.resolve()); repository.createInKeycloak(user).subscribe(() => { expect(repository._sendActivationMail).toHaveBeenCalledWith(user.id); @@ -107,7 +107,7 @@ describe('UserRepository', () => { update: jest.fn().mockReturnValue(Promise.resolve()), }; - repository._addUserRoles = jest.fn().mockReturnValue(Promise.resolve()); + repository._updateUserRoles = jest.fn().mockReturnValue(Promise.resolve()); repository._updateUserGroups = jest.fn().mockReturnValue(Promise.resolve()); }); @@ -117,11 +117,11 @@ describe('UserRepository', () => { expect(kcAdminClient.users['update']).toHaveBeenCalledWith({ id: user.id }, omit(user, 'groupIds')); }); - it('should call addUserRoles', fakeAsync(() => { + it('should call updateUserRoles', fakeAsync(() => { repository.saveInKeycloak(user).subscribe(); tick(); - expect(repository._addUserRoles).toHaveBeenCalledWith(user.id, user.clientRoles); + expect(repository._updateUserRoles).toHaveBeenCalledWith(user.id, user.clientRoles); })); it('should call updateUserGroups', (done) => { @@ -241,70 +241,197 @@ describe('UserRepository', () => { }); }); - describe('addUserRoles', () => { - it('should call addUserRolesForClient for admin', async () => { - repository._addUserRolesForClient = jest.fn(); + describe('UpdateUserRoles', () => { + const clientId: string = faker.string.uuid(); + + beforeEach(() => { + repository._updateUserRolesForClient = jest.fn(); + repository._getClientId = jest.fn().mockReturnValue(clientId); + }); - await repository._addUserRoles(user.id, { admin: [UserFormService.ADMIN], alfa: [] }); + it('should call getClientId for admin', async () => { + await repository._updateUserRoles(user.id, { admin: [UserFormService.ADMIN], alfa: [] }); - expect(repository._addUserRolesForClient).toHaveBeenCalledWith( - user.id, - [UserFormService.ADMIN], - UserRepository.ADMIN_CLIENT_NAME, - ); + expect(repository._getClientId).toHaveBeenCalledWith(UserRepository.ADMIN_CLIENT_NAME); }); - it('should call addUserRolesForClient for admin', async () => { - repository._addUserRolesForClient = jest.fn(); + it('should call UpdateUserRolesForClient for admin', async () => { + await repository._updateUserRoles(user.id, { admin: [UserFormService.ADMIN], alfa: [] }); - await repository._addUserRoles(user.id, { alfa: [UserFormService.POSTSTELLE], admin: [] }); + expect(repository._updateUserRolesForClient).toHaveBeenCalledWith(user.id, [UserFormService.ADMIN], clientId); + }); - expect(repository._addUserRolesForClient).toHaveBeenCalledWith( - user.id, - [UserFormService.POSTSTELLE], - UserRepository.ALFA_CLIENT_NAME, - ); + it('should call getClientId for admin', async () => { + await repository._updateUserRoles(user.id, { alfa: [UserFormService.POSTSTELLE], admin: [] }); + + expect(repository._getClientId).toHaveBeenCalledWith(UserRepository.ALFA_CLIENT_NAME); }); - it('should not call addUserRolesForClient if clientRoles alfa and admin are empty', async () => { - repository._addUserRolesForClient = jest.fn(); + it('should call UpdateUserRolesForClient for admin', async () => { + await repository._updateUserRoles(user.id, { alfa: [UserFormService.POSTSTELLE], admin: [] }); + + expect(repository._updateUserRolesForClient).toHaveBeenCalledWith(user.id, [UserFormService.POSTSTELLE], clientId); + }); + }); + + describe('updateUserRolesForClient', () => { + const clientId: string = faker.string.uuid(); + const clientRoles: string[] = times(3, () => faker.word.sample()); + const newClientRoleMappings: RoleMappingPayload[] = clientRoles.map((role: string) => ({ + id: faker.string.uuid(), + name: role, + })); + const oldClientRoleMappings: RoleMappingPayload[] = newClientRoleMappings.slice(1); + const clientRoleMappings: RoleMappings = { newClientRoleMappings, oldClientRoleMappings }; + + beforeEach(() => { + repository._getClientRoleMappings = jest.fn().mockReturnValue(Promise.resolve(clientRoleMappings)); + repository._deleteUserRoles = jest.fn(); + repository._addUserRoles = jest.fn(); + }); + + it('should call getClientRoleMappings', async () => { + await repository._updateUserRolesForClient(user.id, clientRoles, clientId); + + expect(repository._getClientRoleMappings).toHaveBeenCalledWith(user.id, clientId, clientRoles); + }); + + it('should call deleteUserRoles', async () => { + await repository._updateUserRolesForClient(user.id, clientRoles, clientId); + + expect(repository._deleteUserRoles).toHaveBeenCalledWith(user.id, clientRoleMappings, clientId); + }); - await repository._addUserRoles(user.id, { admin: [], alfa: [] }); + it('should call addUserRoles', async () => { + await repository._updateUserRolesForClient(user.id, clientRoles, clientId); - expect(repository._addUserRolesForClient).not.toHaveBeenCalled(); + expect(repository._addUserRoles).toHaveBeenCalledWith(user.id, clientRoleMappings, clientId); }); }); - describe('addUserRolesForClient', () => { + describe('getClientRoleMappings', () => { const clientId: string = faker.string.uuid(); - const roleMapping: RoleMappingPayload[] = [{ id: faker.string.uuid(), name: faker.word.sample() }]; + const newClientRoles: string[] = times(3, () => faker.word.sample()); + const newClientRoleMappings: RoleMappingPayload[] = newClientRoles.map((role: string) => ({ + id: faker.string.uuid(), + name: role, + })); + const oldClientRoleMappings: RoleMappingPayload[] = newClientRoleMappings.slice(1); + const clientRoleMappings: RoleMappings = { newClientRoleMappings, oldClientRoleMappings }; beforeEach(() => { + repository._getOldUserRoleMappings = jest.fn().mockReturnValue(Promise.resolve(oldClientRoleMappings)); repository._getClientId = jest.fn().mockReturnValue(Promise.resolve(clientId)); - repository._mapUserRoles = jest.fn().mockReturnValue(Promise.resolve(roleMapping)); - repository._addUserRolesInKeycloak = jest.fn(); + repository._mapUserRoles = jest.fn().mockReturnValue(Promise.resolve(newClientRoleMappings)); + repository._deleteUserRoles = jest.fn(); + repository._addUserRoles = jest.fn(); + }); + + it('should call mapUserRoles', () => { + repository._getClientRoleMappings(user.id, clientId, newClientRoles); + + expect(repository._mapUserRoles).toHaveBeenCalledWith(clientId, newClientRoles); + }); + + it('should call get old user role mappings', async () => { + await repository._getClientRoleMappings(user.id, clientId, newClientRoles); + + expect(repository._getOldUserRoleMappings).toHaveBeenCalledWith(user.id, clientId); + }); + + it('should return clientRoleMappings', async () => { + const result: RoleMappings = await repository._getClientRoleMappings(user.id, clientId, newClientRoles); + + expect(result).toEqual(clientRoleMappings); + }); + }); + + describe('getOldUserRoleMappings', () => { + const clientId: string = faker.string.uuid(); + const roleMapping: RoleMappingPayload[] = [{ id: faker.string.uuid(), name: faker.word.sample() }]; + + beforeEach(() => { + kcAdminClient.users = <any>{ + listClientRoleMappings: jest.fn().mockReturnValue(Promise.resolve(roleMapping)), + }; + }); + + it('should call users listClientRoleMappings', () => { + repository._getOldUserRoleMappings(user.id, clientId); + + expect(kcAdminClient.users['listClientRoleMappings']).toHaveBeenCalledWith({ id: user.id, clientUniqueId: clientId }); + }); + + it('should return roleMapping', async () => { + const result: RoleMappingPayload[] = await repository._getOldUserRoleMappings(user.id, clientId); + + expect(result).toEqual(roleMapping); + }); + }); + + describe('deleteUserRoles', () => { + const clientId: string = faker.string.uuid(); + const clientRoles: string[] = times(3, () => faker.word.sample()); + const oldClientRoleMappings: RoleMappingPayload[] = clientRoles.map((role: string) => ({ + id: faker.string.uuid(), + name: role, + })); + const newClientRoleMappings: RoleMappingPayload[] = oldClientRoleMappings.slice(1); + const clientRoleMappings: RoleMappings = { newClientRoleMappings, oldClientRoleMappings }; + + beforeEach(() => { + repository._deleteUserRolesInKeycloak = jest.fn(); kcAdminClient.users = <any>{ addClientRoleMappings: jest.fn().mockReturnValue(Promise.resolve()), }; }); - it('should call getClientId', async () => { - await repository._addUserRolesForClient(user.id, [UserFormService.ADMIN], UserRepository.ADMIN_CLIENT_NAME); + it('should call deleteUserRolesInKeycloak', async () => { + await repository._deleteUserRoles(user.id, clientRoleMappings, clientId); - expect(repository._getClientId).toHaveBeenCalled(); + expect(repository._deleteUserRolesInKeycloak).toHaveBeenCalledWith(user.id, clientId, [oldClientRoleMappings[0]]); }); - it('should call getAlfaClientId', async () => { - await repository._addUserRolesForClient(user.id, [UserFormService.ADMIN], UserRepository.ADMIN_CLIENT_NAME); + it('should call not call deleteUserRolesInKeycloak if no roles to delete', async () => { + await repository._deleteUserRoles( + user.id, + { oldClientRoleMappings, newClientRoleMappings: oldClientRoleMappings }, + clientId, + ); - expect(repository._mapUserRoles).toHaveBeenCalledWith(clientId, [UserFormService.ADMIN]); + expect(repository._deleteUserRolesInKeycloak).not.toHaveBeenCalled(); + }); + }); + + describe('addUserRoles', () => { + const clientId: string = faker.string.uuid(); + const clientRoles: string[] = times(3, () => faker.word.sample()); + const newClientRoleMappings: RoleMappingPayload[] = clientRoles.map((role: string) => ({ + id: faker.string.uuid(), + name: role, + })); + const oldClientRoleMappings: RoleMappingPayload[] = newClientRoleMappings.slice(1); + const clientRoleMappings: RoleMappings = { newClientRoleMappings, oldClientRoleMappings }; + + beforeEach(() => { + repository._addUserRolesInKeycloak = jest.fn(); + + kcAdminClient.users = <any>{ + addClientRoleMappings: jest.fn().mockReturnValue(Promise.resolve()), + }; }); it('should call addUserRolesInKeycloak', async () => { - await repository._addUserRolesForClient(user.id, [UserFormService.ADMIN], UserRepository.ADMIN_CLIENT_NAME); + await repository._addUserRoles(user.id, clientRoleMappings, clientId); - expect(repository._addUserRolesInKeycloak).toHaveBeenCalledWith(user.id, clientId, roleMapping); + expect(repository._addUserRolesInKeycloak).toHaveBeenCalledWith(user.id, clientId, [newClientRoleMappings[0]]); + }); + + it('should not call addUserRolesInKeycloak if no roles to add', async () => { + await repository._addUserRoles(user.id, { newClientRoleMappings, oldClientRoleMappings: newClientRoleMappings }, clientId); + + expect(repository._addUserRolesInKeycloak).not.toHaveBeenCalled(); }); }); @@ -332,11 +459,11 @@ describe('UserRepository', () => { describe('mapUserRoles', () => { const clientId: string = faker.string.uuid(); - const clientRoles: RoleRepresentation[] = Array.from({ length: 3 }, () => ({ + const clientRoles: RoleMappingPayload[] = Array.from({ length: 3 }, () => ({ id: faker.string.uuid(), name: faker.word.sample(), })); - const userRoles: string[] = clientRoles.map((role) => role.name).slice(1); + const userRoles: string[] = clientRoles.map((role: RoleMappingPayload): string => role.name).slice(1); beforeEach(() => { kcAdminClient.clients = <any>{ @@ -353,7 +480,7 @@ describe('UserRepository', () => { it('should return roleMapping', async () => { const result: RoleMappingPayload[] = await repository._mapUserRoles(clientId, userRoles); - expect(result).toEqual(clientRoles.slice(1).map((role) => ({ id: role.id, name: role.name }))); + expect(result).toEqual(clientRoles.slice(1)); }); it('should filter roles if they are not in clientRoles', async () => { @@ -383,6 +510,26 @@ describe('UserRepository', () => { }); }); + describe('deleteUserRolesInKeycloak', () => { + beforeEach(() => { + kcAdminClient.users = <any>{ + delClientRoleMappings: jest.fn().mockReturnValue(Promise.resolve()), + }; + }); + + it('should call kcAdminClient users delClientRoleMappings', async () => { + const clientId: string = faker.string.uuid(); + const roles: RoleMappingPayload[] = Array.from({ length: 3 }, () => ({ + id: faker.string.uuid(), + name: faker.word.sample(), + })); + + await repository._deleteUserRolesInKeycloak(user.id, clientId, roles); + + expect(kcAdminClient.users['delClientRoleMappings']).toHaveBeenCalledWith({ id: user.id, clientUniqueId: clientId, roles }); + }); + }); + describe('sendActivationMail', () => { it('should call kcAdminClient users executeActionsEmail', () => { const userId: string = faker.string.uuid(); @@ -415,6 +562,29 @@ describe('UserRepository', () => { }); }); + describe('deleteUser', () => { + const userId: string = faker.string.uuid(); + + beforeEach(() => { + kcAdminClient.users = <any>{ + del: jest.fn().mockReturnValue(Promise.resolve(undefined)), + }; + }); + + it('should call kcAdminClient users del', () => { + repository.deleteUser(userId); + + expect(kcAdminClient.users['del']).toHaveBeenCalledWith({ id: userId }); + }); + + it('should return void', (done) => { + repository.deleteUser(userId).subscribe((result: void) => { + expect(result).toBeUndefined(); + done(); + }); + }); + }); + describe('getUsers', () => { const user: User = createUser(); const userArray: User[] = [user, user, user]; diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.ts index 130176623c25c1cf1f848643b0a5687906fbae34..e5eca5be54120f7fdf51b66d130053a330ced543 100644 --- a/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.ts +++ b/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.ts @@ -21,18 +21,20 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ClientMapping, ClientRoles, User } from '@admin-client/user-shared'; +import { ClientMapping, ClientRoles, RoleMappings, User } from '@admin-client/user-shared'; import { createStateResource, StateResource } from '@alfa-client/tech-shared'; import { inject, Injectable } from '@angular/core'; import KcAdminClient from '@keycloak/keycloak-admin-client'; import ClientRepresentation from '@keycloak/keycloak-admin-client/lib/defs/clientRepresentation'; import GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation'; import MappingsRepresentation from '@keycloak/keycloak-admin-client/lib/defs/mappingsRepresentation'; -import RoleRepresentation, { RoleMappingPayload } from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation'; +import { RoleMappingPayload } from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation'; import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation'; import { isNil, omit } from 'lodash-es'; import { catchError, concatMap, forkJoin, from, map, mergeMap, Observable, tap, throwError } from 'rxjs'; +import * as _ from 'lodash-es'; + @Injectable({ providedIn: 'root', }) @@ -45,7 +47,7 @@ export class UserRepository { public createInKeycloak(user: User): Observable<User> { return from(this.kcAdminClient.users.create(omit(user, 'groupIds'))).pipe( concatMap(async (response: { id: string }): Promise<{ id: string }> => { - await this._addUserRoles(response.id, user.clientRoles); + await this._updateUserRoles(response.id, user.clientRoles); return response; }), tap((response: { id: string }): void => this._sendActivationMail(response.id)), @@ -58,7 +60,7 @@ export class UserRepository { const { groupIds, ...userToSave } = user; return from(this.kcAdminClient.users.update({ id: user.id }, userToSave)).pipe( concatMap(async (): Promise<void> => { - await this._addUserRoles(user.id, user.clientRoles); + await this._updateUserRoles(user.id, user.clientRoles); await this._updateUserGroups(user.id, user.groupIds); }), map((): User => user), @@ -73,8 +75,10 @@ export class UserRepository { } async _getOldUserGroupIds(userId: string): Promise<string[]> { - const oldUserGroupsReps: GroupRepresentation[] = await this.kcAdminClient.users.listGroups({ id: userId }); - return oldUserGroupsReps.map((group: GroupRepresentation): string => group.id); + const oldUserGroupsReps: RoleMappingPayload[] = <RoleMappingPayload[]>( + await this.kcAdminClient.users.listGroups({ id: userId }) + ); + return _.map(oldUserGroupsReps, 'id'); } async _deleteUserGroups(userId: string, newGroupIds: string[], oldGroupIds: string[]): Promise<void> { @@ -87,7 +91,7 @@ export class UserRepository { ); } - async _addUserGroups(userId: string, newGroupIds: string[], oldGroupIds): Promise<void> { + async _addUserGroups(userId: string, newGroupIds: string[], oldGroupIds: string[]): Promise<void> { await Promise.all( newGroupIds .filter((group) => !oldGroupIds.includes(group)) @@ -97,20 +101,54 @@ export class UserRepository { ); } - async _addUserRoles(userId: string, clientRoles: ClientRoles): Promise<void> { - if (clientRoles.admin.length > 0) { - await this._addUserRolesForClient(userId, clientRoles.admin, UserRepository.ADMIN_CLIENT_NAME); + async _updateUserRoles(userId: string, clientRoles: ClientRoles): Promise<void> { + await this._updateUserRolesForClient(userId, clientRoles.admin, await this._getClientId(UserRepository.ADMIN_CLIENT_NAME)); + await this._updateUserRolesForClient(userId, clientRoles.alfa, await this._getClientId(UserRepository.ALFA_CLIENT_NAME)); + } + + async _updateUserRolesForClient(userId: string, clientRoles: string[], clientId: string): Promise<void> { + const roleMappings: RoleMappings = await this._getClientRoleMappings(userId, clientId, clientRoles); + await this._deleteUserRoles(userId, roleMappings, clientId); + await this._addUserRoles(userId, roleMappings, clientId); + } + + async _getClientRoleMappings(userId: string, clientId: string, clientRoles: string[]): Promise<RoleMappings> { + const newClientRoleMappings: RoleMappingPayload[] = await this._mapUserRoles(clientId, clientRoles); + const oldClientRoleMappings: RoleMappingPayload[] = await this._getOldUserRoleMappings(userId, clientId); + return { newClientRoleMappings, oldClientRoleMappings }; + } + + async _getOldUserRoleMappings(userId: string, clientId: string): Promise<RoleMappingPayload[]> { + return <RoleMappingPayload[]>await this.kcAdminClient.users.listClientRoleMappings({ + id: userId, + clientUniqueId: clientId, + }); + } + + async _deleteUserRoles(userId: string, roleMappings: RoleMappings, clientId: string): Promise<void> { + const rolesToDelete: RoleMappingPayload[] = this.getRolesToDelete(roleMappings); + if (rolesToDelete.length > 0) { + await this._deleteUserRolesInKeycloak(userId, clientId, rolesToDelete); } + } + + private getRolesToDelete(roleMappings: RoleMappings): RoleMappingPayload[] { + return roleMappings.oldClientRoleMappings.filter( + (role: RoleMappingPayload) => !_.map(roleMappings.newClientRoleMappings, 'name').includes(role.name), + ); + } - if (clientRoles.alfa.length > 0) { - await this._addUserRolesForClient(userId, clientRoles.alfa, UserRepository.ALFA_CLIENT_NAME); + async _addUserRoles(userId: string, roleMappings: RoleMappings, clientId: string): Promise<void> { + const rolesToAdd: RoleMappingPayload[] = this.getRolesToAdd(roleMappings); + if (rolesToAdd.length > 0) { + await this._addUserRolesInKeycloak(userId, clientId, rolesToAdd); } } - async _addUserRolesForClient(userId: string, userRoles: string[], client: string): Promise<void> { - const clientId: string = await this._getClientId(client); - const roles: RoleMappingPayload[] = await this._mapUserRoles(clientId, userRoles); - await this._addUserRolesInKeycloak(userId, clientId, roles); + private getRolesToAdd(roleMappings: RoleMappings): RoleMappingPayload[] { + return roleMappings.newClientRoleMappings.filter( + (role: RoleMappingPayload) => !_.map(roleMappings.oldClientRoleMappings, 'name').includes(role.name), + ); } async _getClientId(client: string): Promise<string | undefined> { @@ -119,10 +157,8 @@ export class UserRepository { } async _mapUserRoles(clientId: string, userRoles: string[]): Promise<RoleMappingPayload[]> { - const roles: RoleRepresentation[] = await this.kcAdminClient.clients.listRoles({ id: clientId }); - return roles - .filter((role: RoleRepresentation): boolean => userRoles.includes(role.name)) - .map((role: RoleRepresentation): RoleMappingPayload => ({ id: role.id, name: role.name })); + const roles: RoleMappingPayload[] = <RoleMappingPayload[]>await this.kcAdminClient.clients.listRoles({ id: clientId }); + return roles.filter((role: RoleMappingPayload): boolean => userRoles.includes(role.name)); } async _addUserRolesInKeycloak(userId: string, clientId: string, roles: RoleMappingPayload[]): Promise<void> { @@ -133,6 +169,14 @@ export class UserRepository { }); } + async _deleteUserRolesInKeycloak(userId: string, clientId: string, roles: RoleMappingPayload[]): Promise<void> { + await this.kcAdminClient.users.delClientRoleMappings({ + id: userId, + clientUniqueId: clientId, + roles, + }); + } + _sendActivationMail(userId: string): void { this.kcAdminClient.users.executeActionsEmail({ id: userId, @@ -151,6 +195,10 @@ export class UserRepository { return throwError(() => new Error('An error occurred while saving the user.')); } + public deleteUser(userId: string): Observable<void> { + return from(this.kcAdminClient.users.del({ id: userId })); + } + public getUsers(): Observable<User[]> { return from(this.kcAdminClient.users.find()).pipe( map((userReps: UserRepresentation[]): User[] => @@ -204,7 +252,7 @@ export class UserRepository { private getUserGroups(user: User): Observable<string[]> { return from(this.kcAdminClient.users.listGroups({ id: user.id })).pipe( - map((groups: GroupRepresentation[]): string[] => groups.map((group: GroupRepresentation): string => group.name)), + map((groups: GroupRepresentation[]): string[] => _.map(groups, 'name')), ); } @@ -226,6 +274,6 @@ export class UserRepository { return []; } - return clientMappingsAlfa.mappings.map((role: RoleRepresentation): string => role.name); + return _.map(clientMappingsAlfa.mappings, 'name'); } } diff --git a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.model.ts b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.model.ts index 3a93c42e6b0042f02dd9f509969444431ce6c374..5fac205bca0b5ebb2b5b025ad8450da89cfd8793 100644 --- a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.model.ts +++ b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.model.ts @@ -26,3 +26,8 @@ export interface AdminOrganisationsEinheit { name: string; attributes: { [key: string]: string[] }; } + +export interface OrganisationsEinheitDeleteDialogData { + organisationsEinheitName: string; + organisationsEinheitId: string; +} diff --git a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.spec.ts b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.spec.ts index db8b44f6aad3242e172a407fa6731ce992a60fcc..12b297f16e2cebbc8e774df56fe5d878fdcd6adb 100644 --- a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.spec.ts +++ b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.spec.ts @@ -23,8 +23,12 @@ */ import { AdminOrganisationsEinheit, AdminOrganisationsEinheitService } from '@admin-client/organisations-einheit-shared'; import { AdminOrganisationsEinheitRepository } from '@admin/keycloak-shared'; +import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; import { Mock, mock } from '@alfa-client/test-utils'; import { TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker'; +import { of } from 'rxjs'; +import { singleColdCompleted } from '../../../../tech-shared/test/marbles'; import { createAdminOrganisationsEinheit } from '../test/organisations-einheit'; describe('AdminOrganisationsEinheitService', () => { @@ -55,4 +59,23 @@ describe('AdminOrganisationsEinheitService', () => { expect(repository.create).toHaveBeenCalledWith(organisationsEinheit); }); }); + + describe('deleteInKeycloak', () => { + const organisationsEinheitId: string = faker.string.uuid(); + + it('should call repository delete', () => { + service._deleteInKeycloak(organisationsEinheitId); + + expect(repository.delete).toHaveBeenCalledWith(organisationsEinheitId); + }); + + it('should return result', () => { + const state: StateResource<unknown> = createEmptyStateResource(); + repository.delete.mockReturnValue(of(state)); + + const result = service._deleteInKeycloak(organisationsEinheitId); + + expect(result).toBeObservable(singleColdCompleted(state)); + }); + }); }); diff --git a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.ts b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.ts index 5c12d0613ef05cc74acb0ca4ee61794c61c5e95c..35f7580e9c46cd6262c7a68973d324e32022dcdc 100644 --- a/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.ts +++ b/alfa-client/libs/admin/organisations-einheit-shared/src/lib/organisations-einheit.service.ts @@ -45,6 +45,6 @@ export class AdminOrganisationsEinheitService extends KeycloakResourceService<Ad } _deleteInKeycloak(id: string): Observable<void> { - throw new Error('Method not implemented.'); + return this.repository.delete(id); } } diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.html b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..21de75aa26f03bde92ecc76d8a80a6e901565f7a --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.html @@ -0,0 +1,7 @@ +<admin-organisations-einheit-delete-dialog + [organisationsEinheitName]="organisationsEinheitName" + [deleteStateResource]="deleteStateResource$ | async" + (cancel)="closeDialog()" + (delete)="delete()" + data-test-id="organisations-einheit-delete-dialog" +/> diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.spec.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fad1b24d54d5e650b57182de2ce9f7751dddecb6 --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.spec.ts @@ -0,0 +1,145 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminOrganisationsEinheitService } from '@admin-client/organisations-einheit-shared'; +import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; +import { dispatchEventFromFixture, getMockComponent, Mock, mock } from '@alfa-client/test-utils'; +import { OzgcloudDialogService } from '@alfa-client/ui'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; +import { faker } from '@faker-js/faker'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { singleColdCompleted } from 'libs/tech-shared/test/marbles'; +import { Observable, of } from 'rxjs'; +import { OrganisationsEinheitDeleteDialogContainerComponent } from './organisations-einheit-delete-dialog-container.component'; +import { OrganisationsEinheitDeleteDialogComponent } from './organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component'; + +describe('OrganisationsEinheitDeleteDialogContainerComponent', () => { + let component: OrganisationsEinheitDeleteDialogContainerComponent; + let fixture: ComponentFixture<OrganisationsEinheitDeleteDialogContainerComponent>; + + let dialogService: Mock<OzgcloudDialogService>; + let organisationsEinheitService: Mock<AdminOrganisationsEinheitService>; + + const dialogData = { organisationsEinheitName: faker.word.sample(), organisationsEinheitId: faker.string.uuid() }; + + const organisationsEinheitDeleteDialog: string = getDataTestIdOf('organisations-einheit-delete-dialog'); + + beforeEach(() => { + dialogService = mock(OzgcloudDialogService); + organisationsEinheitService = { ...mock(AdminOrganisationsEinheitService), delete: jest.fn() }; + + TestBed.configureTestingModule({ + imports: [OrganisationsEinheitDeleteDialogContainerComponent], + providers: [ + { provide: OzgcloudDialogService, useValue: dialogService }, + { provide: AdminOrganisationsEinheitService, useValue: organisationsEinheitService }, + { + provide: DIALOG_DATA, + useValue: dialogData, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitDeleteDialogContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + describe('organisations einheit delete dialog', () => { + it('should exist', () => { + const stateResource: StateResource<void> = createEmptyStateResource(); + component.deleteStateResource$ = of(stateResource); + component.organisationsEinheitName = dialogData.organisationsEinheitName; + + const deleteDialogComponent: OrganisationsEinheitDeleteDialogComponent = getMockComponent( + fixture, + OrganisationsEinheitDeleteDialogComponent, + ); + + expect(deleteDialogComponent.organisationsEinheitName).toBe(component.organisationsEinheitName); + expect(deleteDialogComponent.deleteStateResource).toEqual(stateResource); + }); + + it('should call closeDialog on cancel emit', () => { + component.closeDialog = jest.fn(); + + dispatchEventFromFixture(fixture, organisationsEinheitDeleteDialog, 'cancel'); + + expect(component.closeDialog).toHaveBeenCalled(); + }); + + it('should call delete on delete emit', () => { + component.delete = jest.fn(); + + dispatchEventFromFixture(fixture, organisationsEinheitDeleteDialog, 'delete'); + + expect(component.delete).toHaveBeenCalled(); + }); + }); + }); + + describe('component', () => { + describe('constructor', () => { + it('should set organisationsEinheitName', () => { + expect(component.organisationsEinheitName).toBe(dialogData.organisationsEinheitName); + }); + + it('should set organisationsEinheitId', () => { + expect(component.organisationsEinheitId).toBe(dialogData.organisationsEinheitId); + }); + }); + + describe('closeDialog', () => { + it('should call dialogService closeAll', () => { + dialogService.closeAll = jest.fn(); + + component.closeDialog(); + + expect(dialogService.closeAll).toHaveBeenCalled(); + }); + }); + + describe('delete', () => { + const stateResource: StateResource<void> = createEmptyStateResource(); + const stateResource$: Observable<StateResource<void>> = of(stateResource); + const loadingStateResource: StateResource<void> = createEmptyStateResource(true); + const loadingStateResource$: Observable<StateResource<void>> = of(loadingStateResource); + + beforeEach(() => { + organisationsEinheitService.delete.mockReturnValue(stateResource$); + }); + + it('should call organisationsEinheitService delete', () => { + component.delete(); + + expect(organisationsEinheitService.delete).toHaveBeenCalledWith(dialogData.organisationsEinheitId); + }); + + it('should set deleteStateResource$', () => { + component.delete(); + + expect(component.deleteStateResource$).toBeObservable(singleColdCompleted(stateResource)); + }); + + it('should close dialog on delete done', () => { + component.delete(); + component.deleteStateResource$.subscribe(); + + expect(dialogService.closeAll).toHaveBeenCalled(); + }); + + it('should NOT close dialog on delete loading', () => { + organisationsEinheitService.delete.mockReturnValue(loadingStateResource$); + + component.delete(); + component.deleteStateResource$.subscribe(); + + expect(dialogService.closeAll).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..01ce6b8eac5000f08ba18da2a7d6480eccafb109 --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component.ts @@ -0,0 +1,49 @@ +import { + AdminOrganisationsEinheitService, + OrganisationsEinheitDeleteDialogData, +} from '@admin-client/organisations-einheit-shared'; +import { createEmptyStateResource, isNotLoading, StateResource } from '@alfa-client/tech-shared'; +import { OzgcloudDialogService } from '@alfa-client/ui'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; +import { AsyncPipe } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { Observable, of, tap } from 'rxjs'; +import { OrganisationsEinheitDeleteDialogComponent } from './organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component'; + +@Component({ + selector: 'admin-organisations-einheit-delete-dialog-container', + standalone: true, + imports: [AsyncPipe, OrganisationsEinheitDeleteDialogComponent], + templateUrl: './organisations-einheit-delete-dialog-container.component.html', +}) +export class OrganisationsEinheitDeleteDialogContainerComponent { + private readonly dialogService = inject(OzgcloudDialogService); + private readonly dialogData: OrganisationsEinheitDeleteDialogData = inject(DIALOG_DATA); + private readonly organisationsEinheitService = inject(AdminOrganisationsEinheitService); + + public organisationsEinheitName: string; + public organisationsEinheitId: string; + + constructor() { + this.organisationsEinheitName = this.dialogData.organisationsEinheitName; + this.organisationsEinheitId = this.dialogData.organisationsEinheitId; + } + + public deleteStateResource$: Observable<StateResource<void>> = of(createEmptyStateResource<void>()); + + public closeDialog(): void { + this.dialogService.closeAll(); + } + + public delete(): void { + this.deleteStateResource$ = this.organisationsEinheitService + .delete(this.organisationsEinheitId) + .pipe(tap((stateResource: StateResource<void>) => this.closeDialogOnDeleteDone(stateResource))); + } + + private closeDialogOnDeleteDone(stateResource: StateResource<void>): void { + if (isNotLoading(stateResource)) { + this.dialogService.closeAll(); + } + } +} diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.html b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.html new file mode 100644 index 0000000000000000000000000000000000000000..1c75a946f561155e6bd95526ffeb58eb4b6cc4c1 --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.html @@ -0,0 +1,28 @@ +<div class="block flex max-w-xl flex-col gap-4 bg-background-100 p-8"> + <h1 class="text-xl">Organisationseinheit</h1> + + <h2 class="text-lg">{{ organisationsEinheitName }}</h2> + + <p> + <span class="font-bold">Achtung:</span> Durch das Entfernen der Organisationseinheit aus dieser Liste wird die + Organisationseinheit auch für alle Benutzer entfernt. + </p> + + <div class="flex justify-between"> + <ods-button + (clickEmitter)="cancel.emit()" + variant="outline" + text="Abbrechen" + dataTestId="dialog-cancel-button" + data-test-id="dialog-cancel-ods-button" + /> + + <ods-button-with-spinner + [stateResource]="deleteStateResource" + (clickEmitter)="delete.emit()" + text="Löschen" + dataTestId="dialog-delete-button" + data-test-id="dialog-delete-ods-button" + /> + </div> +</div> diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.spec.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5099b902cc490b643dd94764f282a66e9b45aead --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.spec.ts @@ -0,0 +1,79 @@ +import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; +import { + dispatchEventFromFixture, + existsAsHtmlElement, + getElementComponentFromFixtureByCss, + MockEvent, +} from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ButtonWithSpinnerComponent } from '@ods/component'; +import { ButtonComponent } from '@ods/system'; +import { MockComponent } from 'ng-mocks'; +import { getDataTestIdOf } from '../../../../../../../../tech-shared/test/data-test'; +import { OrganisationsEinheitDeleteDialogComponent } from './organisations-einheit-delete-dialog.component'; + +describe('OrganisationsEinheitDeleteDialogComponent', () => { + let component: OrganisationsEinheitDeleteDialogComponent; + let fixture: ComponentFixture<OrganisationsEinheitDeleteDialogComponent>; + + const deleteButton: string = getDataTestIdOf('dialog-delete-ods-button'); + const cancelButton: string = getDataTestIdOf('dialog-cancel-ods-button'); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + OrganisationsEinheitDeleteDialogComponent, + MockComponent(ButtonWithSpinnerComponent), + MockComponent(ButtonComponent), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitDeleteDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + describe('cancel button', () => { + it('should exist', () => { + existsAsHtmlElement(fixture, cancelButton); + }); + + it('should emit cancel on click', () => { + component.cancel.emit = jest.fn(); + + dispatchEventFromFixture(fixture, cancelButton, MockEvent.CLICK); + + expect(component.cancel.emit).toHaveBeenCalled(); + }); + }); + + describe('delete button', () => { + it('should exist', () => { + existsAsHtmlElement(fixture, deleteButton); + }); + + it('should have inputs', () => { + const stateResource: StateResource<void> = createEmptyStateResource(); + component.deleteStateResource = stateResource; + + fixture.detectChanges(); + + const button: ButtonWithSpinnerComponent = getElementComponentFromFixtureByCss(fixture, deleteButton); + expect(button.stateResource).toBe(stateResource); + }); + + it('should emit delete on click', () => { + component.delete.emit = jest.fn(); + + dispatchEventFromFixture(fixture, deleteButton, MockEvent.CLICK); + + expect(component.delete.emit).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..bf433fea87b9bd41af8e4e3e3133b46540b40f30 --- /dev/null +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog/organisations-einheit-delete-dialog.component.ts @@ -0,0 +1,18 @@ +import { StateResource } from '@alfa-client/tech-shared'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ButtonWithSpinnerComponent } from '@ods/component'; +import { ButtonComponent } from '@ods/system'; + +@Component({ + selector: 'admin-organisations-einheit-delete-dialog', + standalone: true, + templateUrl: './organisations-einheit-delete-dialog.component.html', + imports: [ButtonWithSpinnerComponent, ButtonComponent], +}) +export class OrganisationsEinheitDeleteDialogComponent { + @Input() organisationsEinheitName: string; + @Input() deleteStateResource: StateResource<void>; + + @Output() cancel: EventEmitter<void> = new EventEmitter<void>(); + @Output() delete: EventEmitter<void> = new EventEmitter<void>(); +} diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.html b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.html index fc5ff5f37637c6c1cf2b9aa687ef25b0678b41f3..a64e70976ffe314813d4a5cea38c9915905dc7ca 100644 --- a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.html +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.html @@ -26,10 +26,22 @@ <ods-list data-test-id="organisations-einheit-list"> @for (organisationsEinheit of organisationsEinheitList; track $index) { <ods-list-item [attr.data-test-id]="(organisationsEinheit.name | convertForDataTest) + '-organisation-item'"> - <dl class="flex-1 basis-3/4 font-semibold"> - <dt class="sr-only">Name</dt> - <dd data-test-id="organisations-einheit-name">{{ organisationsEinheit.name }}</dd> - </dl> + <div class="space-between flex w-full items-center"> + <dl class="flex-1 basis-3/4 font-semibold"> + <dt class="sr-only">Name</dt> + <dd data-test-id="organisations-einheit-name">{{ organisationsEinheit.name }}</dd> + </dl> + <ods-open-dialog-button + [tooltip]="'Organisationseinheit löschen'" + variant="ghost" + size="fit" + dataTestClass="delete-button" + data-test-id="delete-organisations-einheit-dialog-button" + [dialogData]="{ organisationsEinheitName: organisationsEinheit.name, organisationsEinheitId: organisationsEinheit.id }" + > + <ods-delete-icon icon /> + </ods-open-dialog-button> + </div> </ods-list-item> } </ods-list> diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.spec.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.spec.ts index dd2fba6125d358ef26de554cdf79d2afdc40cf9b..c016c3c9c89a5e1767b684775068803afaf5280d 100644 --- a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.spec.ts +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.spec.ts @@ -23,10 +23,11 @@ */ import { AdminOrganisationsEinheit } from '@admin-client/organisations-einheit-shared'; import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; -import { existsAsHtmlElement, getElementFromFixture, mock } from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getElementFromFixture, getElementFromFixtureByType, mock } from '@alfa-client/test-utils'; import { CommonModule } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; +import { OpenDialogButtonComponent } from '@ods/component'; import { ExclamationIconComponent, ListComponent, ListItemComponent } from '@ods/system'; import { getConvertedDataTestIdOf, getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; @@ -39,6 +40,7 @@ describe('OrganisationsEinheitListComponent', () => { const listSelector: string = getDataTestIdOf('organisations-einheit-list'); const listItemSuffux: string = '-organisation-item'; + const deleteButtonTestId: string = getDataTestIdOf('delete-organisations-einheit-dialog-button'); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -48,7 +50,7 @@ describe('OrganisationsEinheitListComponent', () => { useValue: mock(ActivatedRoute), }, ], - imports: [CommonModule, ConvertForDataTestPipe], + imports: [CommonModule, ConvertForDataTestPipe, MockComponent(OpenDialogButtonComponent)], declarations: [ OrganisationsEinheitListComponent, MockComponent(ListComponent), @@ -92,5 +94,27 @@ describe('OrganisationsEinheitListComponent', () => { }); }); }); + + describe('open delete dialog button', () => { + const organisationsEinheit: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(); + + it('should exist', () => { + component.organisationsEinheitList = [organisationsEinheit]; + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, deleteButtonTestId); + }); + + it('should have inputs', () => { + component.organisationsEinheitList = [organisationsEinheit]; + + fixture.detectChanges(); + const dialog: OpenDialogButtonComponent = getElementFromFixtureByType(fixture, OpenDialogButtonComponent); + + expect(dialog.dialogData.organisationsEinheitName).toBe(organisationsEinheit.name); + expect(dialog.dialogData.organisationsEinheitId).toBe(organisationsEinheit.id); + }); + }); }); }); diff --git a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.ts b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.ts index 7efc0bbbe6727d1d558c63bad8ffe6a2d08b9d35..9fabaed44ebd2111a45a4812193ca6fa774202c9 100644 --- a/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.ts +++ b/alfa-client/libs/admin/organisations-einheit/src/lib/organisations-einheit-container/organisations-einheit-list/organisations-einheit-list.component.ts @@ -22,23 +22,27 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { AdminOrganisationsEinheit } from '@admin-client/organisations-einheit-shared'; -import { ConvertForDataTestPipe, ToResourceUriPipe } from '@alfa-client/tech-shared'; +import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; +import { DIALOG_COMPONENT } from '@alfa-client/ui'; import { CommonModule } from '@angular/common'; import { Component, Input } from '@angular/core'; -import { ExclamationIconComponent, ListComponent, ListItemComponent, TooltipDirective } from '@ods/system'; +import { OpenDialogButtonComponent } from '@ods/component'; +import { DeleteIconComponent, ListComponent, ListItemComponent, TooltipDirective } from '@ods/system'; +import { OrganisationsEinheitDeleteDialogContainerComponent } from './organisations-einheit-delete-dialog-container/organisations-einheit-delete-dialog-container.component'; @Component({ selector: 'admin-organisations-einheit-list', templateUrl: './organisations-einheit-list.component.html', standalone: true, + providers: [{ provide: DIALOG_COMPONENT, useValue: OrganisationsEinheitDeleteDialogContainerComponent }], imports: [ CommonModule, ListComponent, ListItemComponent, - ExclamationIconComponent, - TooltipDirective, - ToResourceUriPipe, ConvertForDataTestPipe, + OpenDialogButtonComponent, + TooltipDirective, + DeleteIconComponent, ], }) export class OrganisationsEinheitListComponent { diff --git a/alfa-client/libs/admin/postfach-shared/src/lib/postfach.model.ts b/alfa-client/libs/admin/postfach-shared/src/lib/postfach.model.ts index e3ce2fb8bd6153796bea9bf82422718d2324721e..89072ed299d081cfe531c40579b4de859a3e41f1 100644 --- a/alfa-client/libs/admin/postfach-shared/src/lib/postfach.model.ts +++ b/alfa-client/libs/admin/postfach-shared/src/lib/postfach.model.ts @@ -37,3 +37,5 @@ export interface Postfach { } export declare type PostfachResource = SettingItemResource<Postfach>; + +export const POSTFACH_NACHRICHT_UPLOADED_ATTACHMENTS: string = 'POSTFACH_NACHRICHT_UPLOADED_ATTACHMENTS'; diff --git a/alfa-client/libs/admin/postfach/src/lib/postfach-container/postfach-container.component.html b/alfa-client/libs/admin/postfach/src/lib/postfach-container/postfach-container.component.html index 3e8e4b49ae09ba083768eb4a1ffa4155ab1bb49c..4cc1c39f9ceae871719596a641734e16f8f966c4 100644 --- a/alfa-client/libs/admin/postfach/src/lib/postfach-container/postfach-container.component.html +++ b/alfa-client/libs/admin/postfach/src/lib/postfach-container/postfach-container.component.html @@ -1,2 +1,2 @@ -<h1 class="heading-1">Postfach</h1> +<h1 class="heading-1" data-test-id="headline">Postfach</h1> <admin-postfach-form [postfachStateResource]="postfachStateResource$ | async" /> diff --git a/alfa-client/libs/admin/shared/src/index.ts b/alfa-client/libs/admin/shared/src/index.ts index 8f0992f8376dfb02cbd0096e33f516ac819f3d97..4257af281863dad5164a007d329fa6efea4f2c5a 100644 --- a/alfa-client/libs/admin/shared/src/index.ts +++ b/alfa-client/libs/admin/shared/src/index.ts @@ -1,4 +1,5 @@ export * from './lib/admin-cancel-button/admin-cancel-button.component'; +export * from './lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component'; export * from './lib/admin-save-button/admin-save-button.component'; export * from './lib/routes'; export * from './lib/token'; diff --git a/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.html b/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.html new file mode 100644 index 0000000000000000000000000000000000000000..c006d95c841f7f9686e98a762901b99511a7e101 --- /dev/null +++ b/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.html @@ -0,0 +1,3 @@ +<ods-open-dialog-button variant="outline_error" label="Löschen" dataTestId="delete-button"> + <ods-delete-icon icon /> +</ods-open-dialog-button> diff --git a/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.spec.ts b/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3250b9c18a432b0f5e8fe523fdb25d79e82f10be --- /dev/null +++ b/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { OpenDialogButtonComponent } from '@ods/component'; +import { DeleteIconComponent } from '@ods/system'; +import { MockComponent } from 'ng-mocks'; +import { AdminDeleteOpenDialogButtonComponent } from './admin-delete-open-dialog-button.component'; + +describe('AdminDeleteOpenDialogButtonComponent', () => { + let component: AdminDeleteOpenDialogButtonComponent; + let fixture: ComponentFixture<AdminDeleteOpenDialogButtonComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AdminDeleteOpenDialogButtonComponent], + declarations: [MockComponent(OpenDialogButtonComponent), MockComponent(DeleteIconComponent)], + }).compileComponents(); + + fixture = TestBed.createComponent(AdminDeleteOpenDialogButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.ts b/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f9bcd3472723a4cfe91fbc91f897fedd16b76db9 --- /dev/null +++ b/alfa-client/libs/admin/shared/src/lib/admin-delete-open-dialog-button/admin-delete-open-dialog-button.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { OpenDialogButtonComponent } from '@ods/component'; +import { DeleteIconComponent } from '@ods/system'; + +@Component({ + selector: 'admin-delete-open-dialog-button', + standalone: true, + imports: [DeleteIconComponent, OpenDialogButtonComponent], + templateUrl: './admin-delete-open-dialog-button.component.html', +}) +export class AdminDeleteOpenDialogButtonComponent {} diff --git a/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.html b/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.html index 8bd6a5f92932e22006b3b8e2cb353a9c3fa93eb1..ccd9cc7ea4c19f667ba950072f96795375c8cbd9 100644 --- a/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.html +++ b/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.html @@ -2,5 +2,6 @@ (clickEmitter)="submit()" text="Speichern" dataTestId="save-button" + data-test-id="save" [stateResource]="stateResource$ | async" /> diff --git a/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.spec.ts b/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.spec.ts index 52a303f378e824677dd6518126ae838ebd416e3a..baee873bfd7c0db07a9295de75844d638edaead5 100644 --- a/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.spec.ts +++ b/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.spec.ts @@ -4,7 +4,7 @@ import { dispatchEventFromFixture, getMockComponent, Mock, MockEvent } from '@al import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Resource } from '@ngxp/rest'; import { ButtonWithSpinnerComponent } from '@ods/component'; -import { getDataTestIdAttributeOf } from 'libs/tech-shared/test/data-test'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { singleCold } from 'libs/tech-shared/test/marbles'; import { createDummyResource } from 'libs/tech-shared/test/resource'; import { MockComponent } from 'ng-mocks'; @@ -17,7 +17,7 @@ describe('AdminSaveButtonComponent', () => { let formService: Mock<AbstractFormService<Resource>>; - const saveButton: string = getDataTestIdAttributeOf('save-button'); + const saveButton: string = getDataTestIdOf('save'); const stateResource: StateResource<Resource> = createStateResource(createDummyResource()); diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts index a2bc7e4bfe5efdec1af21b9ad1da7deaccf1a21f..3abca44e4a1aa07b3bceb119a0caf010014c02ed 100644 --- a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts +++ b/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts @@ -23,12 +23,11 @@ */ import { AggregationMappingListResource, AggregationMappingService } from '@admin-client/reporting-shared'; import { createStateResource, StateResource } from '@alfa-client/tech-shared'; -import { existsAsHtmlElement, mock, Mock } from '@alfa-client/test-utils'; +import { mock, Mock } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RoutingButtonComponent } from '@ods/component'; import { singleCold } from 'libs/tech-shared/test/marbles'; import { MockComponent } from 'ng-mocks'; -import { getDataTestIdAttributeOf } from '../../../../../tech-shared/test/data-test'; import { createAggregationMappingListResource } from '../../../../reporting-shared/test/aggregation-mapping'; import { StatistikContainerComponent } from './statistik-container.component'; @@ -36,8 +35,6 @@ describe('StatistikContainerComponent', () => { let component: StatistikContainerComponent; let fixture: ComponentFixture<StatistikContainerComponent>; - const evaluateAdditionalFieldsTestId: string = getDataTestIdAttributeOf('weitere-felder-auswerten-button'); - let aggregationMappingService: Mock<AggregationMappingService>; beforeEach(async () => { @@ -68,16 +65,6 @@ describe('StatistikContainerComponent', () => { expect(component).toBeTruthy(); }); - describe('template', () => { - describe('weiter felder auswerten button', () => { - it('should exists', () => { - fixture.detectChanges(); - - existsAsHtmlElement(fixture, evaluateAdditionalFieldsTestId); - }); - }); - }); - describe('on init', () => { const stateResource: StateResource<AggregationMappingListResource> = createStateResource( createAggregationMappingListResource(), diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.html b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.html index 27805c87fe074bae41311083bc1aed20d5aaeaef..ce24e7184c9f832dec08b205237303ce3cd25af7 100644 --- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.html +++ b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.html @@ -7,18 +7,25 @@ [formControlName]="StatistikFieldsFormService.FIELD_FORM_ENGINE_NAME" label="Formengine" placeholder="Tragen Sie hier die Formengine des Formulars ein." - data-test-id="form-engine-name-input" + data-test-id="form-engine-name" + dataTestId="form-engine-name" ></ods-text-editor> <ods-text-editor [formControlName]="StatistikFieldsFormService.FIELD_FORM_ID" label="FormID" placeholder="Tragen Sie hier die FormID des Formulars ein." - data-test-id="form-id-input" + data-test-id="form-id" + dataTestId="form-id" ></ods-text-editor> </div> <statistik-fields-form-mapping /> </form> - <ods-button text="Datenfeld hinzufügen" dataTestId="add-mapping-button" (clickEmitter)="formService.addMapping()"> + <ods-button + text="Datenfeld hinzufügen" + dataTestId="add-mapping-button" + data-test-id="add-mapping" + (clickEmitter)="formService.addMapping()" + > <ods-plus-icon icon class="fill-whitetext" /> </ods-button> diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.spec.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.spec.ts index a0154fd3137315cfbfd12187d70f0ffb43635df5..e124d07cf71b81b1c0ee692d87cd25ed8e8b59cd 100644 --- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.spec.ts +++ b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.spec.ts @@ -6,7 +6,7 @@ import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angul import { TextEditorComponent } from '@ods/component'; import { ButtonComponent, PlusIconComponent } from '@ods/system'; import { MockComponent } from 'ng-mocks'; -import { getDataTestIdAttributeOf, getDataTestIdOf } from '../../../../../tech-shared/test/data-test'; +import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test'; import { AdminCancelButtonComponent } from '../../../../shared/src/lib/admin-cancel-button/admin-cancel-button.component'; import { AdminSaveButtonComponent } from '../../../../shared/src/lib/admin-save-button/admin-save-button.component'; import { AdminStatistikFieldsFormComponent } from './admin-statistik-fields-form.component'; @@ -17,9 +17,9 @@ describe('AdminStatistikFieldsFormComponent', () => { let component: AdminStatistikFieldsFormComponent; let fixture: ComponentFixture<AdminStatistikFieldsFormComponent>; - const formEngineNameInputTestId: string = getDataTestIdOf('form-engine-name-input'); - const formIdInputTestId: string = getDataTestIdOf('form-id-input'); - const addMappingButton: string = getDataTestIdAttributeOf('add-mapping-button'); + const formEngineNameInputTestId: string = getDataTestIdOf('form-engine-name'); + const formIdInputTestId: string = getDataTestIdOf('form-id'); + const addMappingButton: string = getDataTestIdOf('add-mapping'); const formBuilder: FormBuilder = new FormBuilder(); diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.html b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.html index 2ce86b545b1039b795911cf172f154b9e2b97871..569fc993f66170c76ad2cad6c28679b36e86e210 100644 --- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.html +++ b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.html @@ -10,6 +10,7 @@ formControlName="sourcePath" label="Pfad des Datenfeldes" placeholder="Tragen Sie hier den gesamten Pfad des Datenfeldes ein, das Sie auswerten möchten." + [dataTestId]="'mapping-field-' + i" [attr.data-test-id]="'mapping-field-' + i" ></ods-text-editor> <ods-button @@ -19,6 +20,7 @@ destructive="true" (clickEmitter)="formService.removeMapping(i)" [dataTestId]="'remove-mapping-button-' + i" + [attr.data-test-id]="'remove-mapping-' + i" > <ods-delete-icon icon /> </ods-button> diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.spec.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.spec.ts index 61cd006e429951cde3657be7688b30063cc2e6eb..d06746e48f9660298e3c4e2d305a1404cd0e8605 100644 --- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.spec.ts +++ b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.spec.ts @@ -5,7 +5,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { TextEditorComponent } from '@ods/component'; import { ButtonComponent, DeleteIconComponent } from '@ods/system'; -import { getDataTestIdOf, getDynamicDataTestIdAttributOf } from 'libs/tech-shared/test/data-test'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; import { StatistikFieldsFormService } from '../statistik-fields.formservice'; import { AdminStatistikFieldsFormMappingComponent } from './statistik-fields-form-mapping.component'; @@ -15,7 +15,7 @@ describe('AdminStatistikFieldsFormMappingComponent', () => { let fixture: ComponentFixture<AdminStatistikFieldsFormMappingComponent>; const mappingField: string = getDataTestIdOf('mapping-field-0'); - const removeMappingButton: string = getDynamicDataTestIdAttributOf('remove-mapping-button-0'); + const removeMappingButton: string = getDataTestIdOf('remove-mapping-0'); const formBuilder: FormBuilder = new FormBuilder(); diff --git a/alfa-client/libs/admin/user-profile/.eslintrc.json b/alfa-client/libs/admin/user-profile/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..b10f9813a8f5c59432cf245301dc4d01a8031fd1 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "lib", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "lib", + "style": "kebab-case" + } + ] + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/alfa-client/libs/admin/user-profile/README.md b/alfa-client/libs/admin/user-profile/README.md new file mode 100644 index 0000000000000000000000000000000000000000..274c5f872b16cc6d084af4624ac985d6def1ab1d --- /dev/null +++ b/alfa-client/libs/admin/user-profile/README.md @@ -0,0 +1,7 @@ +# admin-user-profile + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test admin-user-profile` to execute the unit tests. diff --git a/alfa-client/libs/admin/user-profile/jest.config.ts b/alfa-client/libs/admin/user-profile/jest.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..8670098e4b2ced9d4dc48f74066897f0cde39ff4 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/jest.config.ts @@ -0,0 +1,21 @@ +export default { + displayName: 'admin-user-profile', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'], + coverageDirectory: '../../../coverage/libs/admin/user-profile', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '<rootDir>/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/alfa-client/libs/admin/user-profile/project.json b/alfa-client/libs/admin/user-profile/project.json new file mode 100644 index 0000000000000000000000000000000000000000..fc4e68c48443f646a90034421e184a837300707c --- /dev/null +++ b/alfa-client/libs/admin/user-profile/project.json @@ -0,0 +1,22 @@ +{ + "name": "admin-user-profile", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/admin/user-profile/src", + "prefix": "lib", + "projectType": "library", + "tags": [], + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": [ + "{workspaceRoot}/coverage/{projectRoot}" + ], + "options": { + "jestConfig": "libs/admin/user-profile/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/alfa-client/libs/admin/user-profile/src/index.ts b/alfa-client/libs/admin/user-profile/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.html b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.html new file mode 100644 index 0000000000000000000000000000000000000000..c3fe2d2837418f91d393a6de8cb98b20b271a0a5 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.html @@ -0,0 +1,3 @@ +<ods-dropdown-menu-link-item caption="Leitfaden für die Administration" text="PDF öffnen" [url]="url"> + <ods-file-icon icon fileType="pdf" size="medium" /> +</ods-dropdown-menu-link-item> diff --git a/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.scss b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..bcbd245ce7ef1ee60e79e99fce4f19f468a8c145 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.scss @@ -0,0 +1,3 @@ +:host { + white-space: nowrap; +} diff --git a/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.spec.ts b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..174d7d1fddc546ec91902bd0ee6d7a2f4509f561 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DropdownMenuLinkItemComponent, FileIconComponent } from '@ods/system'; +import { MockComponent } from 'ng-mocks'; +import { DocumentationComponent } from './documentation.component'; + +describe('DocumentationComponent', () => { + let component: DocumentationComponent; + let fixture: ComponentFixture<DocumentationComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DocumentationComponent, MockComponent(DropdownMenuLinkItemComponent), MockComponent(FileIconComponent)], + }).compileComponents(); + + fixture = TestBed.createComponent(DocumentationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.ts b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3efae263c5f161d036cecce326dd636e292ce25 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/documentation/documentation.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import { DropdownMenuLinkItemComponent, FileIconComponent } from '@ods/system'; + +@Component({ + selector: 'admin-documentation', + templateUrl: './documentation.component.html', + styleUrls: ['./documentation.component.scss'], + standalone: true, + imports: [DropdownMenuLinkItemComponent, FileIconComponent], +}) +export class DocumentationComponent { + @Input() url: string; +} diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.html b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9c5b0aa958eaadea5a44ecb92f5cbe45c05b06e6 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.html @@ -0,0 +1,3 @@ +<ods-dropdown-menu-button-item caption="Abmelden" (clickEmitter)="logout.emit()" data-test-id="popup-logout-button"> + <ods-logout-icon icon class="fill-primary" /> +</ods-dropdown-menu-button-item> diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.spec.ts b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..092b7217e8fe78d548dad54ed264c61c4cb0fdcd --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.spec.ts @@ -0,0 +1,42 @@ +import { dispatchEventFromFixture, MockEvent } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { expect } from '@jest/globals'; +import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test'; +import { AdminUserLogoutButtonComponent } from './admin-user-logout-button.component'; + +describe('AdminUserLogoutButtonComponent', () => { + let component: AdminUserLogoutButtonComponent; + let fixture: ComponentFixture<AdminUserLogoutButtonComponent>; + + const logoutButtonTestId: string = getDataTestIdOf('popup-logout-button'); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AdminUserLogoutButtonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AdminUserLogoutButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + describe('menu button item', () => { + describe('output', () => { + describe('clickEmitter', () => { + it('should emit', () => { + component.logout.emit = jest.fn(); + + dispatchEventFromFixture(fixture, logoutButtonTestId, MockEvent.CLICK); + + expect(component.logout.emit).toHaveBeenCalled(); + }); + }); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.ts b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e3f4cd7f873a43d3012767e30b5f58738cbee69 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-logout-button/admin-user-logout-button.component.ts @@ -0,0 +1,12 @@ +import { Component, EventEmitter, Output } from '@angular/core'; +import { DropdownMenuButtonItemComponent, LogoutIconComponent } from '@ods/system'; + +@Component({ + selector: 'admin-user-logout-button', + standalone: true, + templateUrl: './admin-user-logout-button.component.html', + imports: [DropdownMenuButtonItemComponent, LogoutIconComponent], +}) +export class AdminUserLogoutButtonComponent { + @Output() logout = new EventEmitter<void>(); +} diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.html b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.html new file mode 100644 index 0000000000000000000000000000000000000000..78c636770990371bb05b7e6eef2bcca474ca67f0 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.html @@ -0,0 +1,8 @@ +<div + role="img" + class="flex size-9 items-center justify-center rounded-full border-2 border-transparent bg-ozggray-900 hover:border-primary" +> + <p class="font-semibold text-whitetext" data-test-id="popup-button-content"> + {{ currentUserInitials }} + </p> +</div> diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.spec.ts b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d14d9482666d406fa59e7d97856497e2f3ba930b --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AdminUserMenuButtonComponent } from './admin-user-menu-button.component'; + +describe('AdminUserMenuButtonComponent', () => { + let component: AdminUserMenuButtonComponent; + let fixture: ComponentFixture<AdminUserMenuButtonComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AdminUserMenuButtonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AdminUserMenuButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.ts b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..02e3f12cef1b770fd4ee46a561d5949a84bc3e92 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu-button/admin-user-menu-button.component.ts @@ -0,0 +1,10 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'admin-user-menu-button', + standalone: true, + templateUrl: './admin-user-menu-button.component.html', +}) +export class AdminUserMenuButtonComponent { + @Input() currentUserInitials: string; +} diff --git a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.html b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.html similarity index 66% rename from alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.html rename to alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.html index 29fc9153463819b76fb30d8f2deef1dd77e7a9c4..7f5574fb4b4a3e18aa19c5a44094644825a59433 100644 --- a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile-button-container.component.html +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.html @@ -24,20 +24,15 @@ --> <ods-dropdown-menu buttonClass="rounded-full"> - <div - button-content - role="img" - class="flex size-9 items-center justify-center rounded-full border-2 border-transparent bg-ozggray-900 hover:border-primary" - > - <p class="font-semibold text-whitetext" data-test-id="popup-button-content"> - {{ currentUserInitials }} - </p> - </div> - <ods-dropdown-menu-button-item - caption="Abmelden" - (itemClicked)="authenticationService.logout()" - data-test-id="popup-logout-button" - > - <ods-logout-icon icon /> - </ods-dropdown-menu-button-item> + <admin-user-menu-button button-content [currentUserInitials]="currentUserInitials"></admin-user-menu-button> + + <admin-user-logout-button (logout)="authenticationService.logout()"></admin-user-logout-button> + + @if (apiRootStateResource.resource | hasLink: ApiRootLinkRel.DOCUMENTATIONS) { + <div class="h-2"></div> + <admin-documentation + [url]="apiRootStateResource.resource | getUrl: ApiRootLinkRel.DOCUMENTATIONS" + data-test-id="admin-documentation" + /> + } </ods-dropdown-menu> diff --git a/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.spec.ts b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb23876d9563a75acb6816a5cc5cca5285b2480a --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile-button-container.component.spec.ts @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import { ApiRootLinkRel, ApiRootResource } from '@alfa-client/api-root-shared'; +import { createStateResource, StateResource } from '@alfa-client/tech-shared'; +import { dispatchEventFromFixtureByType, existsAsHtmlElement, getElementComponentFromFixtureByCss, getElementFromFixture, mock, Mock, notExistsAsHtmlElement, } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AuthenticationService } from '@authentication'; +import { expect } from '@jest/globals'; +import { getUrl } from '@ngxp/rest'; +import { DropdownMenuButtonItemComponent, DropdownMenuComponent, LogoutIconComponent } from '@ods/system'; +import { MockComponent } from 'ng-mocks'; +import { createApiRootResource } from '../../../../../api-root-shared/test/api-root'; +import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test'; +import { DocumentationComponent } from '../documentation/documentation.component'; +import { AdminUserLogoutButtonComponent } from '../user-logout-button/admin-user-logout-button.component'; +import { UserProfileButtonContainerComponent } from './user-profile.button-container.component'; + +describe('UserProfileButtonContainerComponent', () => { + let component: UserProfileButtonContainerComponent; + let fixture: ComponentFixture<UserProfileButtonContainerComponent>; + + let authenticationService: Mock<AuthenticationService>; + + const popupButtonContent: string = getDataTestIdOf('popup-button-content'); + const popupLogoutButton: string = getDataTestIdOf('popup-logout-button'); + const documentationTestId: string = getDataTestIdOf('admin-documentation'); + + const apiRootStateResource: StateResource<ApiRootResource> = createStateResource(createApiRootResource()); + + beforeEach(() => { + authenticationService = mock(AuthenticationService); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [UserProfileButtonContainerComponent], + imports: [ + RouterTestingModule, + MockComponent(DropdownMenuComponent), + MockComponent(DropdownMenuButtonItemComponent), + MockComponent(LogoutIconComponent), + ], + providers: [ + { + provide: AuthenticationService, + useValue: authenticationService, + }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserProfileButtonContainerComponent); + component = fixture.componentInstance; + component.apiRootStateResource = apiRootStateResource; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('ngOnInit', () => { + it('should call authService to get current user initials', () => { + component.ngOnInit(); + + expect(authenticationService.getCurrentUserInitials).toHaveBeenCalled(); + }); + }); + + describe('template', () => { + describe('popup button', () => { + it('should show initials', () => { + component.currentUserInitials = 'AV'; + fixture.detectChanges(); + + const popupButtonContentElement: HTMLElement = getElementFromFixture(fixture, popupButtonContent); + + expect(popupButtonContentElement.textContent.trim()).toEqual('AV'); + }); + }); + + describe('logout', () => { + it('should call authService logout', () => { + dispatchEventFromFixtureByType(fixture, AdminUserLogoutButtonComponent, 'logout'); + + expect(authenticationService.logout).toHaveBeenCalled(); + }); + }); + + describe('documentation', () => { + it('should exists', () => { + component.apiRootStateResource = createStateResource(createApiRootResource([ApiRootLinkRel.DOCUMENTATIONS])); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, documentationTestId); + }); + + it('should NOT exists', () => { + component.apiRootStateResource = createStateResource(createApiRootResource([])); + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, documentationTestId); + }); + + it('should have inputs', () => { + component.apiRootStateResource = createStateResource(createApiRootResource([ApiRootLinkRel.DOCUMENTATIONS])); + + fixture.detectChanges(); + const documentationComponent: DocumentationComponent = getElementComponentFromFixtureByCss(fixture, documentationTestId); + + expect(documentationComponent.url).toEqual( + getUrl(component.apiRootStateResource.resource, ApiRootLinkRel.DOCUMENTATIONS), + ); + }); + }); + }); +}); diff --git a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile.button-container.component.ts b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component.ts similarity index 59% rename from alfa-client/apps/admin/src/common/user-profile-button-container/user-profile.button-container.component.ts rename to alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component.ts index f2f4bd8351b8eb6be525ce81a68c8aec3c081a6e..6a7d7299beab272fc36cd93602f449dd0bff1af7 100644 --- a/alfa-client/apps/admin/src/common/user-profile-button-container/user-profile.button-container.component.ts +++ b/alfa-client/libs/admin/user-profile/src/lib/user-menu/user-profile.button-container.component.ts @@ -21,20 +21,36 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, OnInit } from '@angular/core'; -import { DropdownMenuButtonItemComponent, DropdownMenuComponent, LogoutIconComponent } from '@ods/system'; +import { ApiRootLinkRel, ApiRootResource } from '@alfa-client/api-root-shared'; +import { GetUrlPipe, HasLinkPipe, StateResource } from '@alfa-client/tech-shared'; +import { Component, inject, Input, OnInit } from '@angular/core'; import { AuthenticationService } from '@authentication'; +import { DropdownMenuComponent } from '@ods/system'; +import { DocumentationComponent } from '../documentation/documentation.component'; +import { AdminUserLogoutButtonComponent } from '../user-logout-button/admin-user-logout-button.component'; +import { AdminUserMenuButtonComponent } from '../user-menu-button/admin-user-menu-button.component'; @Component({ selector: 'user-profile-button-container', templateUrl: './user-profile-button-container.component.html', standalone: true, - imports: [DropdownMenuComponent, DropdownMenuButtonItemComponent, LogoutIconComponent], + imports: [ + DropdownMenuComponent, + HasLinkPipe, + GetUrlPipe, + DocumentationComponent, + AdminUserLogoutButtonComponent, + AdminUserMenuButtonComponent, + ], }) export class UserProfileButtonContainerComponent implements OnInit { + @Input() apiRootStateResource: StateResource<ApiRootResource>; + public currentUserInitials: string; - constructor(public authenticationService: AuthenticationService) {} + public readonly authenticationService: AuthenticationService = inject(AuthenticationService); + + public readonly ApiRootLinkRel = ApiRootLinkRel; ngOnInit(): void { this.currentUserInitials = this.authenticationService.getCurrentUserInitials(); diff --git a/alfa-client/libs/admin/user-profile/src/test-setup.ts b/alfa-client/libs/admin/user-profile/src/test-setup.ts new file mode 100644 index 0000000000000000000000000000000000000000..c408668266d2fec3a9803c0ec044bc163fb987fe --- /dev/null +++ b/alfa-client/libs/admin/user-profile/src/test-setup.ts @@ -0,0 +1,12 @@ +import '@testing-library/jest-dom'; +import 'jest-preset-angular/setup-jest'; + +import { getTestBed } from '@angular/core/testing'; +import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; + +getTestBed().resetTestEnvironment(); +getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { + teardown: { destroyAfterEach: false }, + errorOnUnknownProperties: true, + errorOnUnknownElements: true, +}); diff --git a/alfa-client/libs/admin/user-profile/tsconfig.json b/alfa-client/libs/admin/user-profile/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..8ca9ad312c2bd4dc364383853ddd91a2ed8f86fd --- /dev/null +++ b/alfa-client/libs/admin/user-profile/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "target": "es2022" + } +} diff --git a/alfa-client/libs/admin/user-profile/tsconfig.lib.json b/alfa-client/libs/admin/user-profile/tsconfig.lib.json new file mode 100644 index 0000000000000000000000000000000000000000..8441346f6e5858b2ef4235cb3c3160eda256f94a --- /dev/null +++ b/alfa-client/libs/admin/user-profile/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/alfa-client/libs/admin/user-profile/tsconfig.spec.json b/alfa-client/libs/admin/user-profile/tsconfig.spec.json new file mode 100644 index 0000000000000000000000000000000000000000..e637bf83b59243f9ebc9b37842cfbf3f159bba47 --- /dev/null +++ b/alfa-client/libs/admin/user-profile/tsconfig.spec.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/alfa-client/libs/admin/user-shared/src/lib/user.model.ts b/alfa-client/libs/admin/user-shared/src/lib/user.model.ts index db185a0209450a60d50e2ffc89a263d62f480d12..f496e42b8734907ab060fe4872fc91cb29b14c42 100644 --- a/alfa-client/libs/admin/user-shared/src/lib/user.model.ts +++ b/alfa-client/libs/admin/user-shared/src/lib/user.model.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import RoleRepresentation from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation'; +import RoleRepresentation, { RoleMappingPayload } from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation'; export interface User { id?: string; @@ -43,3 +43,8 @@ export interface ClientRoles { export interface ClientMapping { mappings: RoleRepresentation[]; } + +export interface RoleMappings { + newClientRoleMappings: RoleMappingPayload[]; + oldClientRoleMappings: RoleMappingPayload[]; +} diff --git a/alfa-client/libs/admin/user-shared/src/lib/user.service.spec.ts b/alfa-client/libs/admin/user-shared/src/lib/user.service.spec.ts index 28e7e8cce9ce062979c43175587834aa818724ea..fce4d7d2cc427160ab70357168b5fbc347e7df8e 100644 --- a/alfa-client/libs/admin/user-shared/src/lib/user.service.spec.ts +++ b/alfa-client/libs/admin/user-shared/src/lib/user.service.spec.ts @@ -101,4 +101,21 @@ describe('UserService', () => { expect(user).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: userStateResource })); }); }); + + describe('deleteInKeycloak', () => { + it('should call userRepository delete', () => { + service._deleteInKeycloak(user.id); + + expect(repository.deleteUser).toHaveBeenCalledWith(user.id); + }); + + it('should return void', (done) => { + repository.deleteUser.mockReturnValue(of(undefined)); + + service._deleteInKeycloak(user.id).subscribe((result: void) => { + expect(result).toBeUndefined(); + done(); + }); + }); + }); }); diff --git a/alfa-client/libs/admin/user-shared/src/lib/user.service.ts b/alfa-client/libs/admin/user-shared/src/lib/user.service.ts index e927702221df953f2c5882fa6e8791ca98e0929e..d65103108f19ee160c78a8897f447ce7621b2bb0 100644 --- a/alfa-client/libs/admin/user-shared/src/lib/user.service.ts +++ b/alfa-client/libs/admin/user-shared/src/lib/user.service.ts @@ -47,7 +47,7 @@ export class UserService extends KeycloakResourceService<User> { } _deleteInKeycloak(id: string): Observable<void> { - throw new Error('Method not implemented.'); + return this.userRepository.deleteUser(id); } public getUserById(userId: string): Observable<StateResource<User>> { diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..b49a93cae8a97f0cf12a2e10c50abacc140ab8b6 --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.html @@ -0,0 +1 @@ +<admin-delete-open-dialog-button /> \ No newline at end of file diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec691fc665e848da93f5c649141ec8d34c180e61 --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserFormDeleteButtonContainerComponent } from './user-form-delete-button-container.component'; + +describe('UserFormDeleteButtonComponent', () => { + let component: UserFormDeleteButtonContainerComponent; + let fixture: ComponentFixture<UserFormDeleteButtonContainerComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserFormDeleteButtonContainerComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UserFormDeleteButtonContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c66a5500b2e177a0f0a39aedad3f1ccd5e7e3e9 --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-button/user-form-delete-button-container.component.ts @@ -0,0 +1,13 @@ +import { AdminDeleteOpenDialogButtonComponent } from '@admin-client/shared'; +import { DIALOG_COMPONENT } from '@alfa-client/ui'; +import { Component } from '@angular/core'; +import { UserDeleteDialogContainerComponent } from '../user-form-delete-dialog-container/user-delete-dialog-container.component'; + +@Component({ + selector: 'admin-user-form-delete-container-button', + standalone: true, + imports: [AdminDeleteOpenDialogButtonComponent], + providers: [{ provide: DIALOG_COMPONENT, useValue: UserDeleteDialogContainerComponent }], + templateUrl: './user-form-delete-button-container.component.html', +}) +export class UserFormDeleteButtonContainerComponent {} diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..367d0a4abbbd1f76c76468fe03e27403935a7577 --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.html @@ -0,0 +1,6 @@ +<admin-user-delete-dialog + [username]="formService.getUserName()" + [deleteUserStateResource]="deleteUserStateResource$ | async" + (delete)="deleteUser()" + data-test-id="delete-dialog" +/> diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..474c396b29e6f618186fafc524113a0a19c18097 --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.spec.ts @@ -0,0 +1,124 @@ +import { ROUTES } from '@admin-client/shared'; +import { UserService } from '@admin-client/user-shared'; +import { NavigationService } from '@alfa-client/navigation-shared'; +import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; +import { dispatchEventFromFixture, getMockComponent, Mock, mock } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker/locale/de'; +import { Observable, of } from 'rxjs'; +import { getDataTestIdOf } from '../../../../../../tech-shared/test/data-test'; +import { singleColdCompleted } from '../../../../../../tech-shared/test/marbles'; +import { createUserFormGroup } from '../../../../test/form'; +import { UserFormService } from '../user.formservice'; +import { UserDeleteDialogContainerComponent } from './user-delete-dialog-container.component'; +import { UserDeleteDialogComponent } from './user-delete-dialog/user-delete-dialog.component'; + +describe('UserDeleteDialogContainerComponent', () => { + let component: UserDeleteDialogContainerComponent; + let fixture: ComponentFixture<UserDeleteDialogContainerComponent>; + + const deletDialogLocator: string = getDataTestIdOf('delete-dialog'); + + let formService: Mock<UserFormService>; + let userService: Mock<UserService>; + let navigationService: Mock<NavigationService>; + + beforeEach(async () => { + formService = mock(UserFormService); + userService = mock(UserService); + navigationService = mock(NavigationService); + formService = { + ...mock(UserFormService), + form: createUserFormGroup(), + delete: jest.fn(), + } as any; + + await TestBed.configureTestingModule({ + imports: [UserDeleteDialogContainerComponent, UserDeleteDialogComponent], + providers: [ + { provide: UserFormService, useValue: formService }, + { provide: UserService, useValue: userService }, + { provide: NavigationService, useValue: navigationService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(UserDeleteDialogContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('user form delete dialog', () => { + it('should be called', () => { + const username: string = faker.word.sample(); + const stateResource: StateResource<unknown> = createEmptyStateResource(); + formService.getUserName = jest.fn().mockReturnValue(username); + component.deleteUserStateResource$ = of(stateResource); + + fixture.detectChanges(); + + const deleteDialog: UserDeleteDialogComponent = getMockComponent(fixture, UserDeleteDialogComponent); + expect(deleteDialog.username).toBe(username); + expect(deleteDialog.deleteUserStateResource).toBe(stateResource); + }); + + it('should call deleteUser on emit delete', () => { + component.deleteUser = jest.fn(); + + dispatchEventFromFixture(fixture, deletDialogLocator, 'delete'); + + expect(component.deleteUser).toHaveBeenCalled(); + }); + }); + + describe('component', () => { + describe('deleteUser', () => { + const userId: string = faker.string.uuid(); + const loadingStateResource: StateResource<unknown> = createEmptyStateResource(true); + const loadingStateResource$: Observable<StateResource<unknown>> = of(loadingStateResource); + const loadingDoneStateResource$: Observable<StateResource<unknown>> = of(createEmptyStateResource()); + + beforeEach(() => { + userService.delete = jest.fn().mockReturnValue(loadingStateResource$); + formService.getId = jest.fn().mockReturnValue(userId); + }); + + it('should call formService getId', () => { + component.deleteUser(); + + expect(formService.getId).toHaveBeenCalled(); + }); + + it('should call userService delete', () => { + component.deleteUser(); + + expect(userService.delete).toHaveBeenCalledWith(userId); + }); + + it('should set deleteUserStateResource$', () => { + component.deleteUser(); + + expect(component.deleteUserStateResource$).toBeObservable(singleColdCompleted(loadingStateResource)); + }); + + it('should not navigate on loading', () => { + component.deleteUser(); + component.deleteUserStateResource$.subscribe(); + + expect(navigationService.navigate).not.toHaveBeenCalled(); + }); + + it('should navigate on delete success', () => { + userService.delete.mockReturnValue(loadingDoneStateResource$); + + component.deleteUser(); + component.deleteUserStateResource$.subscribe(); + + expect(navigationService.navigate).toHaveBeenCalledWith(ROUTES.BENUTZER); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..fb3f0e0a9ae2e286b4ab6e49d4f4e79965b65dc5 --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog-container.component.ts @@ -0,0 +1,35 @@ +import { ROUTES } from '@admin-client/shared'; +import { UserService } from '@admin-client/user-shared'; +import { NavigationService } from '@alfa-client/navigation-shared'; +import { createEmptyStateResource, isNotLoading, StateResource } from '@alfa-client/tech-shared'; +import { AsyncPipe } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { Observable, of, tap } from 'rxjs'; +import { UserFormService } from '../user.formservice'; +import { UserDeleteDialogComponent } from './user-delete-dialog/user-delete-dialog.component'; + +@Component({ + selector: 'admin-delete-dialog-container', + standalone: true, + imports: [UserDeleteDialogComponent, AsyncPipe], + templateUrl: './user-delete-dialog-container.component.html', +}) +export class UserDeleteDialogContainerComponent { + public readonly formService = inject(UserFormService); + public readonly userService = inject(UserService); + public readonly navigationService = inject(NavigationService); + + public deleteUserStateResource$: Observable<StateResource<unknown>> = of(createEmptyStateResource()); + + public deleteUser(): void { + this.deleteUserStateResource$ = this.userService + .delete(this.formService.getId()) + .pipe(tap((state: StateResource<unknown>) => this.navigateOnDeleteSuccess(state))); + } + + private navigateOnDeleteSuccess(state: StateResource<any>): void { + if (isNotLoading(state)) { + this.navigationService.navigate(ROUTES.BENUTZER); + } + } +} diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.html new file mode 100644 index 0000000000000000000000000000000000000000..83b4694f02ef4fb61dbb8b65d0e83e114b69840d --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.html @@ -0,0 +1,16 @@ +<div class="block bg-background-100 flex flex-col gap-4 p-8"> + <p>Sind Sie sicher, dass sie <span class="font-bold">{{ username }}</span> löschen möchten?</p> + <p>Hinweis: Die zugewiesenen Vorgänge bleiben bestehen.</p> + + <div class="flex justify-between"> + <ods-cancel-dialog-button data-test-id="dialog-cancel-button-host" /> + <ods-button-with-spinner + [stateResource]="deleteUserStateResource" + (clickEmitter)="delete.emit()" + variant="primary" + text="Löschen" + dataTestId="dialog-delete" + data-test-id="dialog-delete-button-host" + /> + </div> +</div> diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e2cfff1676a8048f0c8cc896450e25437533e39d --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.spec.ts @@ -0,0 +1,57 @@ +import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; +import { dispatchEventFromFixture, existsAsHtmlElement, getMockComponent, MockEvent } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ButtonWithSpinnerComponent, CancelDialogButtonComponent } from '@ods/component'; +import { MockComponent } from 'ng-mocks'; +import { getDataTestIdOf } from '../../../../../../../tech-shared/test/data-test'; +import { UserDeleteDialogComponent } from './user-delete-dialog.component'; + +describe('UserFormDeleteDialogComponent', () => { + let component: UserDeleteDialogComponent; + let fixture: ComponentFixture<UserDeleteDialogComponent>; + + const deleteButton: string = getDataTestIdOf('dialog-delete-button-host'); + const cancelButton: string = getDataTestIdOf('dialog-cancel-button-host'); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserDeleteDialogComponent], + declarations: [MockComponent(CancelDialogButtonComponent), MockComponent(ButtonWithSpinnerComponent)], + }).compileComponents(); + + fixture = TestBed.createComponent(UserDeleteDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + describe('cancel button', () => { + it('should exist', () => { + existsAsHtmlElement(fixture, cancelButton); + }); + }); + + describe('delete button', () => { + it('should exist', () => { + const stateResource: StateResource<unknown> = createEmptyStateResource(); + component.deleteUserStateResource = stateResource; + + fixture.detectChanges(); + + expect(getMockComponent(fixture, ButtonWithSpinnerComponent).stateResource).toBe(stateResource); + }); + + it('should emit delete on click', () => { + component.delete.emit = jest.fn(); + + dispatchEventFromFixture(fixture, deleteButton, MockEvent.CLICK); + + expect(component.delete.emit).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ced40bf330256dd8bd455248717390d1fdc4b62 --- /dev/null +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-delete-dialog-container/user-delete-dialog/user-delete-dialog.component.ts @@ -0,0 +1,16 @@ +import { StateResource } from '@alfa-client/tech-shared'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ButtonWithSpinnerComponent, CancelDialogButtonComponent } from '@ods/component'; + +@Component({ + selector: 'admin-user-delete-dialog', + standalone: true, + imports: [ButtonWithSpinnerComponent, CancelDialogButtonComponent], + templateUrl: './user-delete-dialog.component.html', +}) +export class UserDeleteDialogComponent { + @Input() username: string; + @Input() deleteUserStateResource: StateResource<unknown>; + + @Output() delete: EventEmitter<void> = new EventEmitter<void>(); +} diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-headline/user-form-headline.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-headline/user-form-headline.component.html index 56c74fd10047e5dece25229b0888dbbe70f9ed12..c352d3863a3f95b25eabe0b448501702592f2db1 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-headline/user-form-headline.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-headline/user-form-headline.component.html @@ -1,7 +1,7 @@ -<h1 class="heading-1 mb-4"> - @if(isPatch){ +<h1 class="heading-1 mb-4" data-test-id="benutzer-form-headline"> + @if (isPatch) { Benutzer bearbeiten } @else { Benutzer anlegen } -</h1> \ No newline at end of file +</h1> diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html index 98a2303f351d72dfe65d7710ec73d7566cfdacd7..a8931fbcb6be03bbd9b0a5c97b7194e2e9605453 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html @@ -5,30 +5,52 @@ <h3 class="text-md block font-medium text-text">Administration</h3> <div class="flex items-center gap-2"> <ods-checkbox-editor [formControlName]="UserFormService.ADMIN" label="Admin" inputId="admin" /> - <ods-info-icon - tooltip='Wird nur in Kombination mit "User" verwendet. Diese Rolle kann Funktionen in Keycloak und der Administration konfigurieren, z.B. Benutzer anlegen, Gruppen erstellen bzw. Organisationseinheiten hinzufügen und Rollen zuweisen.' + <button + data-test-id="admin-role-info-button" + tooltip="Diese Rolle kann Funktionen der OZG-Cloud konfigurieren, z.B. Benutzer anlegen, Organisationseinheiten hinzufügen und Rollen zuweisen." + > + <ods-info-icon /> + </button> + </div> + <div class="flex items-center gap-2"> + <ods-checkbox-editor + [formControlName]="UserFormService.DATENBEAUFTRAGUNG" + label="Datenbeauftragung" + inputId="datenbeauftragung" /> + <button + data-test-id="datenbeauftragung-role-info-button" + tooltip='Diese Rolle kann in der Administration unter dem Menüpunkt "Statistik" Felder zur Auswertung konfigurieren. Sie ist mit allen anderen Rollen kompatibel.' + > + <ods-info-icon /> + </button> </div> </div> <div [formGroupName]="UserFormService.ALFA_GROUP" class="flex flex-col gap-2"> <h3 class="text-md block font-medium text-text">Alfa</h3> <div class="flex items-center gap-2"> <ods-checkbox-editor [formControlName]="UserFormService.LOESCHEN" label="Löschen" inputId="delete" /> - <ods-info-icon - tooltip='Diese Rolle hat dieselben Rechte wie die Rolle "User". Zusätzlich kann "Löschen" ausgewählte Vorgänge aus Alfa löschen. Diese Rolle sollten zwei Benutzer haben, da das Löschen einem Vieraugen-Prinzip folgt.' - /> + <button + data-test-id="loschen-role-info-button" + tooltip='Diese Rolle hat dieselben Rechte wie die Rolle "User". Zusätzlich kann "Löschen" Löschanträge aus Alfa bestätigen. ' + > + <ods-info-icon /> + </button> </div> <div class="flex items-center gap-2"> <ods-checkbox-editor [formControlName]="UserFormService.USER" label="User" inputId="user" /> - <ods-info-icon - tooltip='Diese Rolle kann alle Vorgänge sehen und bearbeiten, wenn diese seiner Organisationseinheit zugewiesen sind. Ist kompatibel mit "Löschen" und "Admin".' - /> + <button + data-test-id="user-role-info-button" + tooltip="Diese Rolle kann alle Vorgänge sehen und bearbeiten, wenn diese seiner Organisationseinheit zugewiesen sind." + > + <ods-info-icon /> + </button> </div> <div class="flex items-center gap-2"> <ods-checkbox-editor [formControlName]="UserFormService.POSTSTELLE" label="Poststelle" inputId="post_office" /> - <ods-info-icon - tooltip='Diese Rolle kann ausschließlich alle neu eingegangenen Vorgänge sehen und anderen Benutzern zuweisen. Sie sollte nur einem Benutzer zugewiesen sein. Dieser sollte keine weiteren Rollen besitzen. (Sie ist aber kompatibel mit der "Admin")' - /> + <button data-test-id="poststelle-role-info-button" tooltip="Diese Rolle kann alle neu eingegangenen Vorgänge sehen."> + <ods-info-icon /> + </button> </div> </div> </div> diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-save-button/user-form-save-button.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-save-button/user-form-save-button.component.html index 0277da75dbb4169dd33e4927d89702b1105ce9d9..064289aa47b5e6ff3e9e37656407d30cd0d52c8d 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-save-button/user-form-save-button.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-save-button/user-form-save-button.component.html @@ -3,4 +3,4 @@ (clickEmitter)="submit()" text="Speichern" dataTestId="save-button" -/> \ No newline at end of file +/> diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.html index dcab3c25b5a05434e0eedd7f56e889914253b5d7..5ce59646fde16f7fd3299848b2615f318685e536 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.html @@ -32,6 +32,11 @@ [formGroupParent]="formService.form" [formGroupOrganisationsEinheiten]="formService.getOrganisationsEinheitenGroup()" /> - <admin-user-form-save-button /> + <div class="flex justify-between"> + <admin-user-form-save-button /> + @if (formService.isPatch()) { + <admin-user-form-delete-container-button data-test-id="delete-button-container"/> + } + </div> </div> </ods-spinner> diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.spec.ts index 085f2fe196fdb6d881240f4485527e72123b51ba..160ab43ceea91f4c2fd18bc37ca506e99edfa033 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.spec.ts @@ -23,7 +23,8 @@ */ import { User } from '@admin-client/user-shared'; import { createEmptyStateResource, createStateResource, StateResource } from '@alfa-client/tech-shared'; -import { getMockComponent, mock, Mock, notExistsAsHtmlElement } from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getMockComponent, mock, Mock, notExistsAsHtmlElement } from '@alfa-client/test-utils'; +import { DIALOG_COMPONENT } from '@alfa-client/ui'; import { CommonModule } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormGroup, ReactiveFormsModule } from '@angular/forms'; @@ -47,6 +48,7 @@ describe('UserFormComponent', () => { let formService: Mock<UserFormService>; const userContent: string = getDataTestIdOf('user-content'); + const deleteButtonContainer: string = getDataTestIdOf('delete-button-container'); beforeEach(async () => { formService = <any>{ @@ -67,7 +69,7 @@ describe('UserFormComponent', () => { MockComponent(UserFormRolesComponent), MockComponent(UserFormHeadlineComponent), ], - providers: [{ provide: UserFormService, useValue: formService }], + providers: [{ provide: DIALOG_COMPONENT, useValue: {} }], }) .overrideComponent(UserFormComponent, { set: { @@ -130,7 +132,6 @@ describe('UserFormComponent', () => { it('should exist with input', () => { const formDataComponent: UserFormDataComponent = getMockComponent(fixture, UserFormDataComponent); - expect(formDataComponent).toBeTruthy(); expect(formDataComponent.formGroupParent).toBe(component.formService.form); }); }); @@ -151,7 +152,6 @@ describe('UserFormComponent', () => { UserFormOrganisationsEinheitListComponent, ); - expect(organisationsEinheitListComponent).toBeTruthy(); expect(organisationsEinheitListComponent.formGroupParent).toBe(component.formService.form); expect(organisationsEinheitListComponent.formGroupOrganisationsEinheiten).toBe( component.formService.getOrganisationsEinheitenGroup(), @@ -167,4 +167,22 @@ describe('UserFormComponent', () => { notExistsAsHtmlElement(fixture, userContent); }); }); + + describe('admin delete button container', () => { + it('should exist', () => { + formService.isPatch.mockReturnValue(true); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, deleteButtonContainer); + }); + + it('should not exist', () => { + formService.isPatch.mockReturnValue(false); + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, deleteButtonContainer); + }); + }); }); diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.ts index 03199641945f4841fcd0360b025262c2136d9ef3..a533ff55e37698883943902df16183dc776b22c2 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form.component.ts @@ -29,6 +29,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { SpinnerComponent } from '@ods/component'; import { Observable } from 'rxjs'; import { UserFormDataComponent } from './user-form-data/user-form-data.component'; +import { UserFormDeleteButtonContainerComponent } from './user-form-delete-button/user-form-delete-button-container.component'; import { UserFormHeadlineComponent } from './user-form-headline/user-form-headline.component'; import { UserFormOrganisationsEinheitListComponent } from './user-form-organisations-einheit-list/user-form-organisations-einheit-list.component'; import { UserFormRolesComponent } from './user-form-roles/user-form-roles.component'; @@ -44,12 +45,13 @@ import { UserFormService } from './user.formservice'; FormsModule, ReactiveFormsModule, AsyncPipe, + SpinnerComponent, UserFormDataComponent, UserFormRolesComponent, UserFormOrganisationsEinheitListComponent, UserFormHeadlineComponent, UserFormSaveButtonComponent, - SpinnerComponent, + UserFormDeleteButtonContainerComponent, ], }) export class UserFormComponent implements OnInit { diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts index 8e823be03c1f064fe2cd152da724bb3e0fcdc001..724564f208c9f26cb6265da1bbad81b52ad92b13 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts @@ -26,11 +26,16 @@ import { ROUTES } from '@admin-client/shared'; import { User, UserService } from '@admin-client/user-shared'; import { PatchConfig } from '@admin/keycloak-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { createEmptyStateResource, createStateResource, EMPTY_ARRAY, StateResource } from '@alfa-client/tech-shared'; -import { Mock, mock } from '@alfa-client/test-utils'; +import { + createEmptyStateResource, + createLoadingStateResource, + createStateResource, + StateResource, +} from '@alfa-client/tech-shared'; +import { Mock, mock, mockWindowError } from '@alfa-client/test-utils'; import { SnackBarService } from '@alfa-client/ui'; import { fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { AbstractControl, FormControl, FormGroup, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { ActivatedRoute, UrlSegment } from '@angular/router'; import { faker } from '@faker-js/faker/locale/de'; import { cold } from 'jest-marbles'; @@ -45,6 +50,7 @@ import SpyInstance = jest.SpyInstance; describe('UserFormService', () => { let formService: UserFormService; + let roleGroup: UntypedFormGroup; let alfaGroup: UntypedFormGroup; let organisationsEinheitenGroup: UntypedFormGroup; @@ -62,14 +68,21 @@ describe('UserFormService', () => { ]); beforeEach(() => { - service = { ...mock(UserService), refresh: jest.fn(), create: jest.fn(), save: jest.fn(), getUserById: jest.fn() }; + service = { + ...mock(UserService), + refresh: jest.fn(), + create: jest.fn(), + save: jest.fn(), + delete: jest.fn(), + getUserById: jest.fn(), + }; adminOrganisationsEinheitService = { ...mock(AdminOrganisationsEinheitService), getAll: jest.fn().mockReturnValue(of(adminOrganisationsEinheitList)), }; navigationService = mock(NavigationService); snackBarService = mock(SnackBarService); - activatedRoute = <any>{ ...mock(ActivatedRoute), url: of<UrlSegment[]>(EMPTY_ARRAY) }; + activatedRoute = <any>{ ...mock(ActivatedRoute), url: of<UrlSegment[]>([]) }; TestBed.configureTestingModule({ providers: [ @@ -151,7 +164,13 @@ describe('UserFormService', () => { }); describe('initOrganisationsEinheiten', () => { + beforeEach(() => { + formService._addOrganisationsEinheitenToForm = jest.fn(); + }); + it('should call adminOrganisationsEinheitService getAll', () => { + formService._initOrganisationsEinheiten(); + expect(adminOrganisationsEinheitService.getAll).toHaveBeenCalled(); }); @@ -162,8 +181,6 @@ describe('UserFormService', () => { }); it('should call addOrganisationsEinheitenToForm ', fakeAsync(() => { - formService._addOrganisationsEinheitenToForm = jest.fn(); - formService._initOrganisationsEinheiten().subscribe(); tick(); @@ -181,6 +198,15 @@ describe('UserFormService', () => { it('should set initOrganisationsEinheiten$', () => { expect(formService['_initOrganisationsEinheiten$']).toBeDefined(); }); + + it('should not throw any exception on loading state resource', () => { + adminOrganisationsEinheitService.getAll.mockReturnValue(of(createLoadingStateResource())); + const errorMock: any = mockWindowError(); + + formService._initOrganisationsEinheiten(); + + expect(errorMock).not.toHaveBeenCalled(); + }); }); describe('addOrganisationsEinheitenToForm', () => { @@ -456,4 +482,14 @@ describe('UserFormService', () => { expect(formService._initOrganisationsEinheiten$.unsubscribe).toHaveBeenCalled(); }); }); + + describe('get userName', () => { + it('should return form control value of userName', () => { + formService.form = new FormGroup({ [UserFormService.USERNAME]: new FormControl('userNameDummy') }); + + const userName: string = formService.getUserName(); + + expect(userName).toBe('userNameDummy'); + }); + }); }); diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts index e7e537b362cf5938842d80e3ededcf7297665b78..494673b77cf049e6219418f7a726173af20e3fd0 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts @@ -26,7 +26,7 @@ import { ROUTES } from '@admin-client/shared'; import { User, UserService } from '@admin-client/user-shared'; import { KeycloakFormService, PatchConfig } from '@admin/keycloak-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { createEmptyStateResource, EMPTY_STRING, mapToResource, StateResource } from '@alfa-client/tech-shared'; +import { createEmptyStateResource, EMPTY_STRING, isLoaded, mapToResource, StateResource } from '@alfa-client/tech-shared'; import { SnackBarService } from '@alfa-client/ui'; import { Injectable, OnDestroy } from '@angular/core'; import { @@ -39,7 +39,7 @@ import { Validators, } from '@angular/forms'; import { UrlSegment } from '@angular/router'; -import { catchError, Observable, of, Subscription, tap } from 'rxjs'; +import { catchError, filter, Observable, of, Subscription, tap } from 'rxjs'; @Injectable() export class UserFormService extends KeycloakFormService<User> implements OnDestroy { @@ -52,6 +52,7 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest public static readonly GROUPS: string = 'groups'; public static readonly ADMINISTRATION_GROUP: string = 'admin'; public static readonly ADMIN: string = 'ADMIN_ADMIN'; + public static readonly DATENBEAUFTRAGUNG: string = 'DATENBEAUFTRAGUNG'; public static readonly ALFA_GROUP: string = 'alfa'; public static readonly LOESCHEN: string = 'VERWALTUNG_LOESCHEN'; public static readonly USER: string = 'VERWALTUNG_USER'; @@ -100,6 +101,7 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest { [UserFormService.ADMINISTRATION_GROUP]: this.formBuilder.group({ [UserFormService.ADMIN]: new FormControl(false), + [UserFormService.DATENBEAUFTRAGUNG]: new FormControl(false), }), [UserFormService.ALFA_GROUP]: this.formBuilder.group({ [UserFormService.LOESCHEN]: new FormControl(false), @@ -137,6 +139,7 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest _initOrganisationsEinheiten(): Observable<AdminOrganisationsEinheit[]> { const organisationsEinheitenGroup: UntypedFormGroup = this.getOrganisationsEinheitenGroup(); return this.adminOrganisationsEinheitService.getAll().pipe( + filter(isLoaded), mapToResource<AdminOrganisationsEinheit[]>(), tap((organisationsEinheiten: AdminOrganisationsEinheit[]): void => { this.setOrganisationsEinheitenIdsInMap(organisationsEinheiten); @@ -263,4 +266,8 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest ngOnDestroy(): void { this._initOrganisationsEinheiten$.unsubscribe(); } + + public getUserName(): string { + return this.form.get(UserFormService.USERNAME).value; + } } diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.spec.ts index e3b7bcedafc499a5bf7187d096e9bcb1eb9d6105..e03fe86039ad5c895d6be78eb173a56e78aa445e 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.spec.ts @@ -20,6 +20,7 @@ describe('UserListContainerComponent', () => { userService = { ...mock(UserService), getAll: jest.fn().mockReturnValue(usersStateResource$), + refresh: jest.fn(), }; await TestBed.configureTestingModule({ @@ -55,4 +56,12 @@ describe('UserListContainerComponent', () => { expect(userList.usersStateResource).toBe(usersStateResource); }); }); + + describe('on destroy', () => { + it('should call service to refresh list', () => { + component.ngOnDestroy(); + + expect(userService.refresh).toHaveBeenCalled(); + }); + }); }); diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.ts index 371df8b2664808b39e524439d4ae45d63016f177..ac2e37357acff04c7849031dec3d2cefe30f6a44 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list-container.component.ts @@ -1,8 +1,8 @@ import { User, UserService } from '@admin-client/user-shared'; -import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; +import { StateResource } from '@alfa-client/tech-shared'; import { AsyncPipe } from '@angular/common'; -import { Component, inject, OnInit } from '@angular/core'; -import { Observable, of } from 'rxjs'; +import { Component, inject, OnDestroy, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; import { UserListComponent } from './user-list/user-list.component'; @Component({ @@ -11,12 +11,16 @@ import { UserListComponent } from './user-list/user-list.component'; imports: [UserListComponent, AsyncPipe], templateUrl: './user-list-container.component.html', }) -export class UserListContainerComponent implements OnInit { +export class UserListContainerComponent implements OnInit, OnDestroy { private userService = inject(UserService); - public usersStateResource$: Observable<StateResource<User[]>> = of(createEmptyStateResource<User[]>()); + public usersStateResource$: Observable<StateResource<User[]>>; ngOnInit(): void { this.usersStateResource$ = this.userService.getAll(); } + + ngOnDestroy(): void { + this.userService.refresh(); + } } diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.html b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.html index 153e3e19deecd0653b83ae9fc7e516f85b6a8716..e2db54f1168e688391e37bd141c058e60dd45022 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.html @@ -23,10 +23,12 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<h1 class="heading-1 mb-4">Benutzer & Rollen</h1> +<h1 class="heading-1 mb-4" data-test-id="user-list-headline">Benutzer & Rollen</h1> <ods-routing-button [linkPath]="ROUTES.BENUTZER_NEU" text="Benutzer hinzufügen" class="mb-4 w-fit" dataTestId="add-user-button" /> -<ods-list> - @for (user of usersStateResource.resource; track $index) { - <admin-user [user]="user" class="block w-full" /> - } -</ods-list> +<ods-spinner [stateResource]="usersStateResource"> + <ods-list data-test-id="user-list"> + @for (user of usersStateResource.resource; track $index) { + <admin-user [user]="user" class="block w-full" /> + } + </ods-list> +</ods-spinner> diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.spec.ts index 23bc4e3efe65add8ea04eaff6d4324b907190dc8..fe161b5c1fc09a5a9b2def8a5a500b261358a9fe 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.spec.ts @@ -26,7 +26,7 @@ import { User } from '@admin-client/user-shared'; import { createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared'; import { getMockComponent } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RoutingButtonComponent } from '@ods/component'; +import { RoutingButtonComponent, SpinnerComponent } from '@ods/component'; import { MockComponent } from 'ng-mocks'; import { createUser } from '../../../../../user-shared/test/user'; import { UserListComponent } from './user-list.component'; @@ -38,8 +38,10 @@ describe('UsersListComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [UserListComponent], - declarations: [MockComponent(RoutingButtonComponent), MockComponent(UserComponent)], + imports: [ + UserListComponent, + [MockComponent(RoutingButtonComponent), MockComponent(UserComponent), MockComponent(SpinnerComponent)], + ], }).compileComponents(); fixture = TestBed.createComponent(UserListComponent); diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.ts index 984d6540945e2739b3b89e66eb9fc03ebc7264d6..61df6b59bfa24732237a7837e9a0d67e7f2f90cc 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user-list.component.ts @@ -26,7 +26,7 @@ import { User } from '@admin-client/user-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { AsyncPipe } from '@angular/common'; import { Component, Input } from '@angular/core'; -import { ButtonWithSpinnerComponent, RoutingButtonComponent } from '@ods/component'; +import { ButtonWithSpinnerComponent, RoutingButtonComponent, SpinnerComponent } from '@ods/component'; import { ListComponent, ListItemComponent } from '@ods/system'; import { UserComponent } from './user/user.component'; @@ -34,7 +34,15 @@ import { UserComponent } from './user/user.component'; selector: 'admin-user-list', templateUrl: './user-list.component.html', standalone: true, - imports: [ButtonWithSpinnerComponent, ListComponent, UserComponent, AsyncPipe, ListItemComponent, RoutingButtonComponent], + imports: [ + ButtonWithSpinnerComponent, + ListComponent, + UserComponent, + AsyncPipe, + ListItemComponent, + RoutingButtonComponent, + SpinnerComponent, + ], }) export class UserListComponent { @Input() usersStateResource: StateResource<User[]>; diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-data/user-data.component.html b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-data/user-data.component.html index 7d455acd981b3767f809810878baea7ebedd5cf1..a09af6b607a48ed6e0bf6bddec4dedae9a1ebd46 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-data/user-data.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-data/user-data.component.html @@ -4,13 +4,13 @@ <span class="sr-only">E-Mail:</span> <ods-mailbox-icon size="small" class="stroke-gray-600" /> </dt> - <dd>{{ user.email }}</dd> + <dd data-test-class="email">{{ user.email }}</dd> </div> <div class="flex items-center gap-2"> <dt> <span class="sr-only">Benutzername:</span> <ods-person-icon /> </dt> - <dd>{{ user.username }}</dd> + <dd data-test-class="username">{{ user.username }}</dd> </div> -</dl> \ No newline at end of file +</dl> diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-organisations-einheiten/user-organisations-einheiten.component.html b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-organisations-einheiten/user-organisations-einheiten.component.html index 550037d391c229139ce5248398ff7715a6e730d4..2836ffd0b61913f495192bfa344d376fb79b6584 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-organisations-einheiten/user-organisations-einheiten.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-organisations-einheiten/user-organisations-einheiten.component.html @@ -1,18 +1,18 @@ <h4 class="sr-only">Zuständige Stellen</h4> @if (organisationsEinheiten.length > 0) { - <ul class="list-outside list-disc pl-4"> - @for(group of organisationsEinheiten | slice: 0 : MAX_GROUPS_TO_DISPLAY; track $index) { + <ul class="list-outside list-disc pl-4" data-test-class="organisations-einheiten"> + @for (group of organisationsEinheiten | slice: 0 : MAX_GROUPS_TO_DISPLAY; track $index) { <li>{{ group }}</li> } </ul> @if (organisationsEinheiten.length > MAX_GROUPS_TO_DISPLAY) { - @if(organisationsEinheiten.length - MAX_GROUPS_TO_DISPLAY === 1 ){ + @if (organisationsEinheiten.length - MAX_GROUPS_TO_DISPLAY === 1) { <p class="pl-4 text-gray-500">und 1 weiterer</p> } @else { <p class="pl-4 text-gray-500">und {{ organisationsEinheiten.length - MAX_GROUPS_TO_DISPLAY }} weitere</p> } } } @else { - <p> keine zuständige Stelle zugewiesen </p> -} \ No newline at end of file + <p data-test-class="no-organisations-einheit-text">keine zuständige Stelle zugewiesen</p> +} diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-roles/user-roles.component.html b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-roles/user-roles.component.html index 26b29dd6d1082879467997191335242a734aac76..f9f7e74372668a5ecf0a74c01808bc675c20327c 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-roles/user-roles.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user-roles/user-roles.component.html @@ -1,4 +1,4 @@ -<dl class="flex flex-wrap gap-2"> +<dl class="flex flex-wrap gap-2" data-test-class="roles"> <dt class="sr-only">Rollen:</dt> @for (role of roles; track $index) { <dd diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.html b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.html index b19df22eb0537d270a9485c8a03b39bf04108939..cda8fb80181aea98d263a1928f683852722a18d7 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.html @@ -1,7 +1,7 @@ -<ods-list-item [path]="user.id" [attr.data-test-id]="'user-entry-' + user.username"> +<ods-list-item [path]="user.id" [attr.data-test-id]="(user.username | convertForDataTest) + '-user-entry'"> <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> + <h3 class="text-md font-semibold" data-test-class="fullname">{{ user | toUserName }}</h3> <admin-user-roles [roles]="user | toUserRoles" /> </div> diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.spec.ts index afd371e8c6812b03a5b6b671545b4e4344c18ac3..42da5233cedfd9c1dd176fb8973f1b7ea388dc48 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.spec.ts @@ -1,3 +1,4 @@ +import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; import { getMockComponent } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ListItemComponent } from '@ods/system'; @@ -17,6 +18,7 @@ describe('UserComponent', () => { await TestBed.configureTestingModule({ imports: [ UserComponent, + ConvertForDataTestPipe, MockComponent(ListItemComponent), MockComponent(UserRolesComponent), MockComponent(UserDataComponent), diff --git a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.ts b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.ts index f46a2dc2950c2f651dc2e313a4831cbf10ca6ba8..49a54feaafe1f7b2444813699b7eeb2a15fc9487 100644 --- a/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-list-container/user-list/user/user.component.ts @@ -1,4 +1,5 @@ import { ToUserNamePipe, ToUserRolesPipe, User } from '@admin-client/user-shared'; +import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; import { NgForOf } from '@angular/common'; import { Component, Input } from '@angular/core'; import { ListItemComponent } from '@ods/system'; @@ -17,6 +18,7 @@ import { UserRolesComponent } from './user-roles/user-roles.component'; UserDataComponent, UserRolesComponent, ToUserRolesPipe, + ConvertForDataTestPipe, ], templateUrl: './user.component.html', }) diff --git a/alfa-client/libs/admin/user/test/form.ts b/alfa-client/libs/admin/user/test/form.ts index 7b3f06850de1ff16504596b38056fb3752b3d5e5..d8ac4bb73c5b96a51e5559e85d8876a1695506aa 100644 --- a/alfa-client/libs/admin/user/test/form.ts +++ b/alfa-client/libs/admin/user/test/form.ts @@ -12,6 +12,7 @@ export function createUserFormGroup(): UntypedFormGroup { [UserFormService.CLIENT_ROLES]: new UntypedFormGroup({ [UserFormService.ADMINISTRATION_GROUP]: new UntypedFormGroup({ [UserFormService.ADMIN]: new FormControl(false), + [UserFormService.DATENBEAUFTRAGUNG]: new FormControl(false), }), [UserFormService.ALFA_GROUP]: new UntypedFormGroup({ [UserFormService.LOESCHEN]: new FormControl(false), diff --git a/alfa-client/libs/authentication/src/lib/authentication.service.spec.ts b/alfa-client/libs/authentication/src/lib/authentication.service.spec.ts index 486415f425de82475e9733ec3f5800cb47128c92..0e2ba5d44dd5054b2fa2b0e651b8205cf0327258 100644 --- a/alfa-client/libs/authentication/src/lib/authentication.service.spec.ts +++ b/alfa-client/libs/authentication/src/lib/authentication.service.spec.ts @@ -23,10 +23,11 @@ */ import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { UserProfileResource } from '@alfa-client/user-profile-shared'; +import { NavigationEnd, Router } from '@angular/router'; import { AuthConfig, OAuthEvent, OAuthService } from 'angular-oauth2-oidc'; import { Environment } from 'libs/environment-shared/src/lib/environment.model'; import { createUserProfileResource } from 'libs/user-profile-shared/test/user-profile'; -import { Subject } from 'rxjs'; +import { of, Subject } from 'rxjs'; import { createEnvironment } from '../../../environment-shared/test/environment'; import { createAuthConfig, createOAuthEvent } from '../../test/authentication'; import { AuthenticationService } from './authentication.service'; @@ -34,6 +35,7 @@ import { AuthenticationService } from './authentication.service'; describe('AuthenticationService', () => { let service: AuthenticationService; let oAuthService: Mock<OAuthService>; + let router: Mock<Router>; let environmentConfig: Environment; let eventsSubject: Subject<OAuthEvent>; @@ -41,6 +43,7 @@ describe('AuthenticationService', () => { beforeEach(() => { eventsSubject = new Subject<OAuthEvent>(); + router = mock(Router); oAuthService = <any>{ ...mock(OAuthService), loadDiscoveryDocumentAndLogin: jest.fn().mockResolvedValue(() => Promise.resolve()), @@ -50,12 +53,19 @@ describe('AuthenticationService', () => { Object.defineProperty(oAuthService, 'events', { get: () => eventsSubject }); environmentConfig = createEnvironment(); - service = new AuthenticationService(useFromMock(oAuthService), environmentConfig); + service = new AuthenticationService(useFromMock(oAuthService), useFromMock(router), environmentConfig); }); describe('login', () => { beforeEach(() => { service.buildAuthEventPromise = jest.fn(); + service._initialNavigationIsDone = jest.fn().mockResolvedValue(() => Promise.resolve()); + }); + + it('should wait for initial navigation', async () => { + await service.login(); + + expect(service._initialNavigationIsDone).toHaveBeenCalled(); }); it('should configure service with authConfig', async () => { @@ -89,8 +99,8 @@ describe('AuthenticationService', () => { expect(service.buildAuthEventPromise).toHaveBeenCalled(); }); - it('should load discovery document and login', () => { - service.login(); + it('should load discovery document and login', async () => { + await service.login(); expect(oAuthService.loadDiscoveryDocumentAndLogin).toHaveBeenCalled(); }); @@ -105,6 +115,16 @@ describe('AuthenticationService', () => { }); }); + describe('_initialNavigationIsDone', () => { + it('should wait for navigation end event', async () => { + (router.events as any) = of(new NavigationEnd(0, 'url1', 'url1')); + + const promise: Promise<void> = service._initialNavigationIsDone(); + + await expect(promise).resolves.toBeUndefined(); + }); + }); + describe('build auth event promise', () => { const event: OAuthEvent = createOAuthEvent(); @@ -350,4 +370,20 @@ describe('AuthenticationService', () => { expect(oAuthService.revokeTokenAndLogout).toHaveBeenCalled(); }); }); + + describe('isLoggedIn', () => { + it('should call oAuthService hasValidAccessToken', () => { + service.isLoggedIn(); + + expect(oAuthService.hasValidAccessToken).toHaveBeenCalled(); + }); + + it('should return result', () => { + oAuthService.hasValidAccessToken.mockReturnValue(true); + + const isLoggedIn: boolean = service.isLoggedIn(); + + expect(isLoggedIn).toBe(true); + }); + }); }); diff --git a/alfa-client/libs/authentication/src/lib/authentication.service.ts b/alfa-client/libs/authentication/src/lib/authentication.service.ts index e350dcafe7cb9312799db97db5eb50c0d25c8319..4918b8f8d7e95c9ff71b2c53a7aa487266e91155 100644 --- a/alfa-client/libs/authentication/src/lib/authentication.service.ts +++ b/alfa-client/libs/authentication/src/lib/authentication.service.ts @@ -23,11 +23,12 @@ */ import { Environment, ENVIRONMENT_CONFIG } from '@alfa-client/environment-shared'; import { Inject, Injectable } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; import { AuthConfig, OAuthEvent, OAuthService } from 'angular-oauth2-oidc'; import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks'; import { UserProfileResource } from 'libs/user-profile-shared/src/lib/user-profile.model'; import { getUserNameInitials } from 'libs/user-profile-shared/src/lib/user-profile.util'; -import { filter, Subscription } from 'rxjs'; +import { filter, firstValueFrom, Subscription } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthenticationService { @@ -37,10 +38,13 @@ export class AuthenticationService { constructor( private oAuthService: OAuthService, + private router: Router, @Inject(ENVIRONMENT_CONFIG) private envConfig: Environment, ) {} public async login(): Promise<void> { + await this._initialNavigationIsDone(); + this.oAuthService.configure(this.buildConfiguration()); this.oAuthService.setupAutomaticSilentRefresh(); this.oAuthService.tokenValidationHandler = new JwksValidationHandler(); @@ -50,6 +54,10 @@ export class AuthenticationService { return eventPromise; } + async _initialNavigationIsDone(): Promise<void> { + await firstValueFrom(this.router.events.pipe(filter((event) => event instanceof NavigationEnd))); + } + buildAuthEventPromise(): Promise<void> { return new Promise<void>((resolve, reject) => this.handleAuthEventsForPromise(resolve, reject)); } @@ -90,7 +98,7 @@ export class AuthenticationService { return { issuer: this.envConfig.authServer + '/realms/' + this.envConfig.realm, tokenEndpoint: this.envConfig.authServer + '/realms/' + this.envConfig.realm + '/protocol/openid-connect/token', - redirectUri: window.location.origin + '/', + redirectUri: window.location.origin + window.location.pathname, clientId: this.envConfig.clientId, scope: 'openid profile', requireHttps: false, @@ -119,4 +127,8 @@ export class AuthenticationService { public logout(): void { this.oAuthService.revokeTokenAndLogout(); } + + public isLoggedIn(): boolean { + return this.oAuthService.hasValidAccessToken(); + } } diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid.model.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid.model.ts index 6fee5ec006a52da9172c6491cc0bf38c4a0ba0df..24cbaccf50af06a9560638b4ea80884a05de7e07 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid.model.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid.model.ts @@ -118,3 +118,5 @@ export function createInitialWizard(): Wizard { bescheidCreated: false, }; } + +export const BESCHEID_UPLOADED_ATTACHMENTS: string = 'bescheid_uploaded_attachments'; \ No newline at end of file diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.spec.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.spec.ts index ab845cf6b4f7e3156ebb7a940e418780e9a410d2..ddb4bc98ad09c5415e2e3ca6c4853002c17c32d7 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.spec.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.spec.ts @@ -26,7 +26,6 @@ import { CommandOrder, CommandResource, CommandService, CreateCommandProps } fro import { PostfachService } from '@alfa-client/postfach-shared'; import { ApiError, - EMPTY_ARRAY, EMPTY_STRING, HttpError, StateResource, @@ -1325,7 +1324,7 @@ describe('BescheidService', () => { it('should return empty array', () => { const resultdBescheide: BescheidResource[] = service.filterBySentStatus(null); - expect(resultdBescheide).toBe(EMPTY_ARRAY); + expect(resultdBescheide).toEqual([]); }); }); diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts index 7b8a2f737d688302a3a9f30e218b126a9fad285e..f2df99ebc8b9b47adb3244134990a129aedfe731 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts @@ -33,7 +33,6 @@ import { } from '@alfa-client/command-shared'; import { PostfachService } from '@alfa-client/postfach-shared'; import { - EMPTY_ARRAY, HttpError, ResourceListService, StateResource, @@ -524,7 +523,7 @@ export class BescheidService { } filterBySentStatus(bescheide: BescheidResource[]): BescheidResource[] { - return isNotNil(bescheide) ? bescheide.filter(this.hasSentStatus) : EMPTY_ARRAY; + return isNotNil(bescheide) ? bescheide.filter(this.hasSentStatus) : []; } private hasSentStatus(bescheid: BescheidResource): boolean { diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid2.service.spec.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid2.service.spec.ts index 977ca30abc3c3200416749391b488b397e75731c..9fbf45a5ca814d6ed15691e08259f1a664f836e0 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid2.service.spec.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid2.service.spec.ts @@ -24,7 +24,7 @@ import { BinaryFileListLinkRel, BinaryFileListResource, BinaryFileResource, BinaryFileService, } from '@alfa-client/binary-file-shared'; import { CommandOrder, CommandResource, CommandResourceService, CommandService, CreateCommandProps, getEffectedResourceUrl, } from '@alfa-client/command-shared'; import { PostfachService } from '@alfa-client/postfach-shared'; -import { createEmptyStateResource, createErrorStateResource, createLoadingStateResource, createStateResource, EMPTY_STRING, getEmbeddedResources, StateResource, } from '@alfa-client/tech-shared'; +import { createEmptyStateResource, createErrorStateResource, createStateResource, EMPTY_STRING, getEmbeddedResources, StateResource, } from '@alfa-client/tech-shared'; import { Mock, mock } from '@alfa-client/test-utils'; import { VorgangCommandService, VorgangService, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; import { TestBed } from '@angular/core/testing'; @@ -39,12 +39,12 @@ import { createCommandErrorStateResource, createCommandResource, createCommandSt import { ResourceRepository } from '../../../tech-shared/src/lib/resource/resource.repository'; import { createFile } from '../../../tech-shared/test/file'; import { singleCold, singleColdCompleted } from '../../../tech-shared/test/marbles'; -import { createBescheid, createBescheidAttachments, createBescheidDocument, createBescheidResource } from '../test/bescheid'; +import { createBescheid, createBescheidDocument, createBescheidResource } from '../test/bescheid'; import { createDocumentResource } from '../test/document'; import { BescheidFacade } from './+state/bescheid.facade'; import { BescheidResourceService } from './bescheid-resource-service'; import { BescheidLinkRel } from './bescheid.linkrel'; -import { Bescheid, BescheidAttachments, BescheidDocument, BescheidResource, BescheidWizardStep, createEmptyBescheidAttachments, createEmptyBescheidDocument, createEmptyUploadInProgress, createInitialWizard, } from './bescheid.model'; +import { Bescheid, BESCHEID_UPLOADED_ATTACHMENTS, BescheidDocument, BescheidResource, BescheidWizardStep, createEmptyBescheidDocument, createEmptyUploadInProgress, createInitialWizard, } from './bescheid.model'; import { BescheidService2 } from './bescheid2.service'; import { DocumentLinkRel } from './document.linkrel'; import { DocumentResource } from './document.model'; @@ -115,16 +115,25 @@ describe('BescheidService', () => { expect(service.getActiveStep()).toBeObservable(singleCold(BescheidWizardStep.AntragBescheiden)); expect(service.getBescheidCreated()).toBeObservable(singleCold(false)); expect(service.getBescheidDocument()).toBeObservable(singleCold(createEmptyBescheidDocument())); - expect(service.getAttachments()).toBeObservable(singleCold(createEmptyBescheidAttachments())); }); }); describe('exit', () => { + beforeEach(() => { + service._clearUploadedFiles = jest.fn(); + }); + it('should reload postfach list', () => { service.exit(); expect(postfachService.setPostfachMailOnReload).toHaveBeenCalled(); }); + + it('should clear uploaded files', () => { + service.exit(); + + expect(service._clearUploadedFiles).toHaveBeenCalled(); + }); }); describe('skipBescheidCreation', () => { @@ -441,113 +450,16 @@ describe('BescheidService', () => { expect(binaryFileService.getFiles).not.toHaveBeenCalled(); }); - it('should emit attachments state', () => { + it('should add files', () => { const binaryFileListStateResource: StateResource<BinaryFileListResource> = createStateResource(createBinaryFileListResource()); binaryFileService.getFiles.mockReturnValue(of(binaryFileListStateResource)); - service.loadAttachments(bescheidResourceWithAttachments); - - expect(binaryFileService.getFiles).toHaveBeenCalledWith(bescheidResourceWithAttachments, BescheidLinkRel.ATTACHMENTS); - - expect(service.getAttachments()).toBeObservable( - singleCold({ - ...createEmptyBescheidAttachments(), - items: getEmbeddedResources(binaryFileListStateResource, BinaryFileListLinkRel.FILE_LIST), - }), - ); - }); - }); - - describe('uploadAttachment', () => { - const attachment: File = createFile(); - - beforeEach(() => { - binaryFileService.uploadFile.mockReturnValue(EMPTY); - service.handleAttachmentUpload = jest.fn(); - }); - - it('should emit upload loading', () => { - service.uploadAttachment(attachment, bescheidResource); - - expect(service.getAttachments()).toBeObservable( - singleCold({ - ...createEmptyBescheidAttachments(), - upload: { fileName: attachment.name, loading: true }, - uploadStateResource: createLoadingStateResource(), - } as BescheidAttachments), - ); - }); - - it('should call binary file service', () => { - service.uploadAttachment(attachment, bescheidResource); - - expect(binaryFileService.uploadFile).toHaveBeenCalledWith( - bescheidResource, - BescheidLinkRel.UPLOAD_ATTACHMENT, - attachment, - false, - ); - }); - - it('should handle attachment upload', () => { - const binaryFileStateResource: StateResource<BinaryFileResource> = createStateResource(createBinaryFileResource()); - binaryFileService.uploadFile.mockReturnValue(of(binaryFileStateResource)); - - service.uploadAttachment(attachment, bescheidResource); - expect(service.handleAttachmentUpload).toHaveBeenCalledWith(binaryFileStateResource); - }); - }); - - describe('handleAttachmentUpload', () => { - it('should emit state on success', () => { - const binaryFileStateResource: StateResource<BinaryFileResource> = createStateResource(createBinaryFileResource()); - - service.handleAttachmentUpload(binaryFileStateResource); - - expect(service.getAttachments()).toBeObservable( - singleCold({ - ...createEmptyBescheidAttachments(), - items: [binaryFileStateResource.resource], - uploadStateResource: binaryFileStateResource, - upload: { - ...createEmptyUploadInProgress(), - error: binaryFileStateResource.error, - loading: binaryFileStateResource.loading, - }, - } as BescheidAttachments), - ); - }); - - it('should emit state on error', () => { - const binaryFileStateResource: StateResource<BinaryFileResource> = createErrorStateResource(createProblemDetail()); - - service.handleAttachmentUpload(binaryFileStateResource); - - expect(service.getAttachments()).toBeObservable( - singleCold({ - ...createEmptyBescheidAttachments(), - items: [], - uploadStateResource: binaryFileStateResource, - upload: { - ...createEmptyUploadInProgress(), - error: binaryFileStateResource.error, - loading: binaryFileStateResource.loading, - }, - }), - ); - }); - }); - - describe('deleteAttachment', () => { - it('should delete', () => { - const binaryFileStateResource: StateResource<BinaryFileResource> = createStateResource(createBinaryFileResource()); - service.handleAttachmentUpload(binaryFileStateResource); - - service.deleteAttachment(binaryFileStateResource.resource); + service.loadAttachments(bescheidResourceWithAttachments); - expect(service.getAttachments()).toBeObservable( - singleCold({ ...createEmptyBescheidAttachments(), uploadStateResource: binaryFileStateResource } as BescheidAttachments), + expect(binaryFileService.addFiles).toHaveBeenCalledWith( + BESCHEID_UPLOADED_ATTACHMENTS, + getEmbeddedResources(binaryFileListStateResource, BinaryFileListLinkRel.FILE_LIST), ); }); }); @@ -885,11 +797,21 @@ describe('BescheidService', () => { }); describe('setActiveStep', () => { + beforeEach(() => { + service._clearUploadedFiles = jest.fn(); + }); + it('should emit changed active step', () => { service.setActiveStep(BescheidWizardStep.DokumenteHochladen); expect(service.getActiveStep()).toBeObservable(singleCold(BescheidWizardStep.DokumenteHochladen)); }); + + it('should clear uploaded files', () => { + service.setActiveStep(BescheidWizardStep.DokumenteHochladen); + + expect(service._clearUploadedFiles).toHaveBeenCalled(); + }); }); describe('getBescheidDraft', () => { @@ -939,23 +861,6 @@ describe('BescheidService', () => { }); }); - describe('finishAddingAttachments', () => { - it('should update state', () => { - const attachments: BescheidAttachments = createBescheidAttachments(); - service._attachments$.next(attachments); - - service.finishAddingAttachments(); - - expect(service.getAttachments()).toBeObservable( - singleCold({ - ...attachments, - upload: createEmptyUploadInProgress(), - uploadStateResource: createEmptyStateResource(), - } as BescheidAttachments), - ); - }); - }); - describe('finishAddingBescheidDocument', () => { it('should update state', () => { const bescheidDocument: BescheidDocument = createBescheidDocument(); @@ -972,4 +877,12 @@ describe('BescheidService', () => { ); }); }); + + describe('clear uploaded files', () => { + it('should call binary files service', () => { + service._clearUploadedFiles(); + + expect(binaryFileService.clearUploadedFiles).toHaveBeenCalledWith(BESCHEID_UPLOADED_ATTACHMENTS); + }); + }); }); diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid2.service.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid2.service.ts index 3c5d8191390993f76b6456a8577d895b57dd6f55..8a48a4dbdf043a0994ac12c2a298a6ae2be2fb3e 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid2.service.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid2.service.ts @@ -1,6 +1,6 @@ import { Bescheid, - BescheidAttachments, + BESCHEID_UPLOADED_ATTACHMENTS, BescheidDocument, BescheidLinkRel, BescheidResource, @@ -11,7 +11,6 @@ import { buildCreateBescheidDocumentFromFileProps, buildSendBescheidCommandProps, buildUpdateBescheidCommandProps, - createEmptyBescheidAttachments, createEmptyBescheidDocument, createEmptyUploadInProgress, createInitialWizard, @@ -35,7 +34,6 @@ import { import { PostfachService } from '@alfa-client/postfach-shared'; import { createEmptyStateResource, - createLoadingStateResource, filterIsLoadedOrHasError, getEmbeddedResources, hasStateResourceError, @@ -63,17 +61,16 @@ export class BescheidService2 { private readonly bescheidResourceService = inject(BescheidResourceService); readonly _bescheidDocument$: BehaviorSubject<BescheidDocument> = new BehaviorSubject(createEmptyBescheidDocument()); - readonly _attachments$: BehaviorSubject<BescheidAttachments> = new BehaviorSubject(createEmptyBescheidAttachments()); readonly _wizard$: BehaviorSubject<Wizard> = new BehaviorSubject(createInitialWizard()); public init(): void { this._wizard$.next(createInitialWizard()); this._bescheidDocument$.next(createEmptyBescheidDocument()); - this._attachments$.next(createEmptyBescheidAttachments()); } public exit(): void { this.postfachService.setPostfachMailOnReload(); + this._clearUploadedFiles(); } public skipBescheidCreation( @@ -173,50 +170,10 @@ export class BescheidService2 { getEmbeddedResources<BinaryFileResource>(stateResource, BinaryFileListLinkRel.FILE_LIST), ), ) - .subscribe((files: BinaryFileResource[]) => this._attachments$.next({ ...this._attachments$.value, items: files })); + .subscribe((files: BinaryFileResource[]) => this.binaryFileService.addFiles(BESCHEID_UPLOADED_ATTACHMENTS, files)); } } - public uploadAttachment(attachment: File, bescheidResource: BescheidResource): void { - this._attachments$.next({ - ...this._attachments$.value, - upload: { fileName: attachment.name, loading: true }, - uploadStateResource: createLoadingStateResource(), - }); - this.binaryFileService - .uploadFile(bescheidResource, BescheidLinkRel.UPLOAD_ATTACHMENT, attachment, false) - .pipe(filterIsLoadedOrHasError(), first()) - .subscribe((binaryFileStateResource: StateResource<BinaryFileResource>) => - this.handleAttachmentUpload(binaryFileStateResource), - ); - } - - handleAttachmentUpload(binaryFileStateResource: StateResource<BinaryFileResource>) { - const value: BescheidAttachments = this._attachments$.value; - if (hasStateResourceError(binaryFileStateResource)) { - this._attachments$.next({ - ...value, - uploadStateResource: binaryFileStateResource, - upload: { ...value.upload, error: binaryFileStateResource.error, loading: binaryFileStateResource.loading }, - }); - } else { - this._attachments$.next({ - ...value, - uploadStateResource: binaryFileStateResource, - items: [...value.items, binaryFileStateResource.resource], - upload: { ...value.upload, error: binaryFileStateResource.error, loading: binaryFileStateResource.loading }, - }); - } - } - - public deleteAttachment(attachment: BinaryFileResource): void { - const value: BescheidAttachments = this._attachments$.value; - this._attachments$.next({ - ...value, - items: value.items.filter((each: BinaryFileResource) => getUrl(each) !== getUrl(attachment)), - }); - } - public uploadBescheidDocument(document: File, bescheid: BescheidResource): void { this._bescheidDocument$.next({ ...this._bescheidDocument$.value, upload: { fileName: document.name, loading: true } }); this.binaryFileService @@ -321,10 +278,6 @@ export class BescheidService2 { return this.bescheidResourceService.get(); } - public getAttachments(): Observable<BescheidAttachments> { - return this._attachments$.asObservable(); - } - public getBescheidDocument(): Observable<BescheidDocument> { return this._bescheidDocument$.asObservable(); } @@ -338,6 +291,7 @@ export class BescheidService2 { } public setActiveStep(step: BescheidWizardStep): void { + this._clearUploadedFiles(); this._wizard$.next({ ...this._wizard$.value, activeStep: step }); } @@ -369,14 +323,6 @@ export class BescheidService2 { this._wizard$.next({ ...this._wizard$.value, canBeSend: true }); } - public finishAddingAttachments(): void { - this._attachments$.next({ - ...this._attachments$.value, - upload: createEmptyUploadInProgress(), - uploadStateResource: createEmptyStateResource(), - }); - } - public finishAddingBescheidDocument(): void { this._bescheidDocument$.next({ ...this._bescheidDocument$.value, @@ -384,4 +330,8 @@ export class BescheidService2 { create: createEmptyStateResource(), }); } + + _clearUploadedFiles(): void { + this.binaryFileService.clearUploadedFiles(BESCHEID_UPLOADED_ATTACHMENTS); + } } diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/bescheid-list-in-vorgang.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/bescheid-list-in-vorgang.component.html index 9689b2ae63c8a501ddaaf0d483212624e7c4fac6..f845d98c061a412f27ba24bbac1b22653cca7046 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/bescheid-list-in-vorgang.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/bescheid-list-in-vorgang.component.html @@ -47,6 +47,7 @@ data-test-class="binary-file-container-in-bescheid" [resource]="bescheid" [linkRel]="bescheidLinkRel.ATTACHMENTS" + [listOrientation]="BinaryFileListOrientation.VERTICAL" ></alfa-binary-file-list-container> </ods-bescheid-wrapper> </div> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/bescheid-list-in-vorgang.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/bescheid-list-in-vorgang.component.ts index 843a0c6a69cc75f34b898e9605049a7128024372..559483edb769643b7e4ecb340429de8e51f12906 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/bescheid-list-in-vorgang.component.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/bescheid-list-in-vorgang.component.ts @@ -23,6 +23,7 @@ */ import { BescheidLinkRel, BescheidListLinkRel, BescheidListResource, BescheidStatus } from '@alfa-client/bescheid-shared'; import { Component, Input } from '@angular/core'; +import { BinaryFileListOrientation } from '../../../../../binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive'; @Component({ selector: 'alfa-bescheid-list-in-vorgang', @@ -32,6 +33,8 @@ import { Component, Input } from '@angular/core'; export class BescheidListInVorgangComponent { @Input() public bescheidList: BescheidListResource; + public readonly BinaryFileListOrientation = BinaryFileListOrientation; + public readonly bescheidListLinkRel = BescheidListLinkRel; public readonly bescheidLinkRel = BescheidLinkRel; public readonly bescheidStatus = BescheidStatus; diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/antrag-bescheiden/form/bescheid-wizard-antrag-bescheiden-form.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/antrag-bescheiden/form/bescheid-wizard-antrag-bescheiden-form.component.html index 8861db5df5fd7745a5061fecd7fead4301583384..2843835f2a178e1e40d221a7bc8b4d14bcca81ea 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/antrag-bescheiden/form/bescheid-wizard-antrag-bescheiden-form.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/antrag-bescheiden/form/bescheid-wizard-antrag-bescheiden-form.component.html @@ -42,7 +42,7 @@ ><ods-close-icon size="large" class="fill-abgelehnt"></ods-close-icon> </ods-radio-button-card> </div> - <div class="flex w-full"> + <div class="flex"> <ozgcloud-date-editor [formControlName]="formServiceClass.FIELD_BESCHIEDEN_AM" label="am" diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.html deleted file mode 100644 index 622107cd6a88a6410e8c7bcdc7e08fd262f1635b..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.html +++ /dev/null @@ -1,21 +0,0 @@ -<ods-attachment-wrapper> - @for (attachment of attachments; track $index) { - <alfa-binary-file2-container - [file]="attachment" - [deletable]="deletable" - (startDelete)="delete.emit($event)" - [attr.data-test-id]="(attachment.name | convertForDataTest) + '-file2-container'" - > - </alfa-binary-file2-container> - } - @if (upload.loading || upload.error) { - <ods-attachment - [loadingCaption]="upload.fileName" - errorCaption="Fehler beim Hochladen" - [errorMessages]="upload.error | convertProblemDetailToErrorMessages" - description="Anhang wird hochgeladen" - [isLoading]="upload.loading" - data-test-id="attachment-upload-in-progress" - ></ods-attachment> - } -</ods-attachment-wrapper> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.spec.ts deleted file mode 100644 index 92f027903e7ddb01410294bcced6f60759882b68..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { BescheidAttachments, createEmptyBescheidAttachments, createEmptyUploadInProgress } from '@alfa-client/bescheid-shared'; -import { BinaryFile2ContainerComponent } from '@alfa-client/binary-file'; -import { ConvertForDataTestPipe, ConvertProblemDetailToErrorMessagesPipe, ProblemDetail } from '@alfa-client/tech-shared'; -import { - existsAsHtmlElement, - getElementComponentFromFixtureByCss, - notExistsAsHtmlElement, - triggerEvent, -} from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AttachmentComponent, AttachmentWrapperComponent } from '@ods/system'; -import { MockComponent } from 'ng-mocks'; -import { createBescheidAttachments } from '../../../../../../../bescheid-shared/src/test/bescheid'; -import { getDataTestIdOf } from '../../../../../../../tech-shared/test/data-test'; -import { createProblemDetail } from '../../../../../../../tech-shared/test/error'; -import { BescheidWizardAttachmentFilesComponent } from './bescheid-wizard-attachment-files.component'; - -describe('BescheidWizardAttachmentFileComponent', () => { - let component: BescheidWizardAttachmentFilesComponent; - let fixture: ComponentFixture<BescheidWizardAttachmentFilesComponent>; - - const convertForDataTest: ConvertForDataTestPipe = new ConvertForDataTestPipe(); - const convertProblemDetailsToErrorMessage: ConvertProblemDetailToErrorMessagesPipe = - new ConvertProblemDetailToErrorMessagesPipe(); - - const attachments: BescheidAttachments = createBescheidAttachments(); - - const binaryFileContainerTestId: string = getDataTestIdOf( - convertForDataTest.transform(attachments.items[0].name) + '-file2-container', - ); - const attachmentUploadTestId: string = getDataTestIdOf('attachment-upload-in-progress'); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - BescheidWizardAttachmentFilesComponent, - MockComponent(BinaryFile2ContainerComponent), - MockComponent(AttachmentComponent), - MockComponent(AttachmentWrapperComponent), - ConvertForDataTestPipe, - ConvertProblemDetailToErrorMessagesPipe, - ], - }).compileComponents(); - - fixture = TestBed.createComponent(BescheidWizardAttachmentFilesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - describe('component', () => { - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should set initial values', () => { - expect(component.upload).toEqual(createEmptyUploadInProgress()); - expect(component.attachments).toEqual(createEmptyBescheidAttachments().items); - }); - - describe('set attachments', () => { - it('should set attachments', () => { - component.attachments = attachments; - - expect(component.attachments).toEqual(attachments.items); - }); - - it('should set upload', () => { - component.attachments = attachments; - - expect(component.upload).toEqual(attachments.upload); - }); - }); - }); - - describe('template', () => { - describe('attachment binary file container', () => { - it('should exists', () => { - component.attachments = attachments; - - fixture.detectChanges(); - - existsAsHtmlElement(fixture, binaryFileContainerTestId); - }); - - it('should have been called with inputs', () => { - component.attachments = attachments; - - fixture.detectChanges(); - const binaryFileComponent: BinaryFile2ContainerComponent = - getElementComponentFromFixtureByCss<BinaryFile2ContainerComponent>(fixture, binaryFileContainerTestId); - - expect(binaryFileComponent.file).toEqual(attachments.items[0]); - expect(binaryFileComponent.deletable).toEqual(component.deletable); - }); - - describe('output', () => { - describe('startDelete', () => { - it('should emit', () => { - component.attachments = attachments; - component.delete.emit = jest.fn(); - - fixture.detectChanges(); - - triggerEvent({ - fixture, - elementSelector: binaryFileContainerTestId, - name: 'startDelete', - data: attachments.items[0], - }); - - expect(component.delete.emit).toHaveBeenCalledWith(attachments.items[0]); - }); - }); - }); - }); - - describe('upload attachment', () => { - it('should exists on loading', () => { - component.attachments = { ...attachments, upload: { ...attachments.upload, loading: true, error: null } }; - - fixture.detectChanges(); - - existsAsHtmlElement(fixture, attachmentUploadTestId); - }); - - it('should exists on error', () => { - component.attachments = { - ...attachments, - upload: { ...attachments.upload, loading: false, error: createProblemDetail() }, - }; - - fixture.detectChanges(); - - existsAsHtmlElement(fixture, attachmentUploadTestId); - }); - - it('should NOT exists', () => { - component.attachments = { - ...attachments, - upload: { ...attachments.upload, loading: false, error: null }, - }; - - fixture.detectChanges(); - - notExistsAsHtmlElement(fixture, attachmentUploadTestId); - }); - - it('should have been called with inputs', () => { - component.attachments = attachments; - - fixture.detectChanges(); - const attachmentComponent: AttachmentComponent = getElementComponentFromFixtureByCss<AttachmentComponent>( - fixture, - attachmentUploadTestId, - ); - - expect(attachmentComponent.loadingCaption).toEqual(attachments.upload.fileName); - expect(attachmentComponent.errorMessages).toEqual( - convertProblemDetailsToErrorMessage.transform(attachments.upload.error as ProblemDetail), - ); - expect(attachmentComponent.isLoading).toEqual(attachments.upload.loading); - }); - }); - }); -}); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.ts deleted file mode 100644 index b5a60cd795a7f6b7b918ce39a5fd5b45c1b3cb63..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - BescheidAttachments, - createEmptyBescheidAttachments, - createEmptyUploadInProgress, - UploadFileInProgress, -} from '@alfa-client/bescheid-shared'; -import { BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ - selector: 'alfa-bescheid-wizard-attachment-files', - templateUrl: './bescheid-wizard-attachment-files.component.html', -}) -export class BescheidWizardAttachmentFilesComponent { - @Input() set attachments(value: BescheidAttachments) { - this._attachments = value; - this.upload = value.upload; - } - - get attachments(): BinaryFileResource[] { - return this._attachments.items; - } - - @Input() deletable: boolean; - - @Output() delete: EventEmitter<BinaryFileResource> = new EventEmitter(); - - public upload: UploadFileInProgress = createEmptyUploadInProgress(); - private _attachments: BescheidAttachments = createEmptyBescheidAttachments(); -} diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.html deleted file mode 100644 index c34bfce43f0959f56aa16e8a993f31b61bec0839..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.html +++ /dev/null @@ -1,6 +0,0 @@ -<alfa-bescheid-wizard-attachment-files - [attachments]="attachments$ | async" - [deletable]="(activeStep$ | async) === bescheidWizardStep.DokumenteHochladen" - (delete)="deleteAttachment($event)" - data-test-id="bescheid-attachments" -></alfa-bescheid-wizard-attachment-files> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.spec.ts deleted file mode 100644 index 9c2cdd8df598bfddfb8b6fc3512066f44d58cc65..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { BescheidAttachments, BescheidWizardStep } from '@alfa-client/bescheid-shared'; -import { BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { existsAsHtmlElement, getElementComponentFromFixtureByCss, mock, Mock, triggerEvent } from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; -import { BescheidService2 } from '../../../../../../bescheid-shared/src/lib/bescheid2.service'; -import { createBescheidAttachments } from '../../../../../../bescheid-shared/src/test/bescheid'; -import { createBinaryFileResource } from '../../../../../../binary-file-shared/test/binary-file'; -import { getDataTestIdOf } from '../../../../../../tech-shared/test/data-test'; -import { singleColdCompleted } from '../../../../../../tech-shared/test/marbles'; -import { BescheidWizardAttachmentFilesComponent } from './attachment-files/bescheid-wizard-attachment-files.component'; -import { BescheidWizardAttachmentFilesContainerComponent } from './bescheid-wizard-attachment-files-container.component'; - -describe('BescheidWizardAttachmentFileContainerComponent', () => { - let component: BescheidWizardAttachmentFilesContainerComponent; - let fixture: ComponentFixture<BescheidWizardAttachmentFilesContainerComponent>; - - const attachmentsFilesTestId: string = getDataTestIdOf('bescheid-attachments'); - - const attachments: BescheidAttachments = createBescheidAttachments(); - - let bescheidService: Mock<BescheidService2>; - - beforeEach(() => { - bescheidService = mock(BescheidService2); - }); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [BescheidWizardAttachmentFilesContainerComponent, MockComponent(BescheidWizardAttachmentFilesComponent)], - providers: [{ provide: BescheidService2, useValue: bescheidService }], - }).compileComponents(); - - fixture = TestBed.createComponent(BescheidWizardAttachmentFilesContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - describe('component', () => { - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('ngOnInit', () => { - it('should set attachments', () => { - bescheidService.getAttachments.mockReturnValue(of(attachments)); - - component.ngOnInit(); - - expect(component.attachments$).toBeObservable(singleColdCompleted(attachments)); - }); - - it('should set active step', () => { - bescheidService.getActiveStep.mockReturnValue(of(BescheidWizardStep.BescheidVersenden)); - - component.ngOnInit(); - - expect(component.activeStep$).toBeObservable(singleColdCompleted(BescheidWizardStep.BescheidVersenden)); - }); - }); - - describe('deleteAttachment', () => { - it('should call service', () => { - component.deleteAttachment = jest.fn(); - const binaryFileResource: BinaryFileResource = createBinaryFileResource(); - - component.deleteAttachment(binaryFileResource); - - expect(component.deleteAttachment).toHaveBeenCalledWith(binaryFileResource); - }); - }); - }); - - describe('template', () => { - describe('attachment files', () => { - function getComponent(): BescheidWizardAttachmentFilesComponent { - return getElementComponentFromFixtureByCss<BescheidWizardAttachmentFilesComponent>(fixture, attachmentsFilesTestId); - } - - it('should exists', () => { - existsAsHtmlElement(fixture, attachmentsFilesTestId); - }); - - it('should have been called with attachments', () => { - component.attachments$ = of(attachments); - - fixture.detectChanges(); - - expect(getComponent().attachments).toEqual(attachments); - }); - - it('should have been called with deletable true', () => { - component.activeStep$ = of(BescheidWizardStep.DokumenteHochladen); - - fixture.detectChanges(); - - expect(getComponent().deletable).toEqual(true); - }); - - it('should have been called with deletable false', () => { - component.activeStep$ = of(BescheidWizardStep.BescheidVersenden); - - fixture.detectChanges(); - - expect(getComponent().deletable).toEqual(false); - }); - - describe('output', () => { - describe('delete', () => { - it('should call handler', () => { - const binaryFileResource: BinaryFileResource = createBinaryFileResource(); - component.deleteAttachment = jest.fn(); - - triggerEvent({ fixture, elementSelector: attachmentsFilesTestId, name: 'delete', data: binaryFileResource }); - - expect(component.deleteAttachment).toHaveBeenCalledWith(binaryFileResource); - }); - }); - }); - }); - }); -}); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.ts deleted file mode 100644 index 50d2dea88ea12ba5fec926d895ff47044f017409..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { BescheidAttachments, BescheidWizardStep } from '@alfa-client/bescheid-shared'; -import { BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { Component, inject, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; -import { BescheidService2 } from '../../../../../../bescheid-shared/src/lib/bescheid2.service'; - -@Component({ - selector: 'alfa-bescheid-wizard-attachment-files-container', - templateUrl: './bescheid-wizard-attachment-files-container.component.html', -}) -export class BescheidWizardAttachmentFilesContainerComponent implements OnInit { - public readonly bescheidService = inject(BescheidService2); - - public attachments$: Observable<BescheidAttachments>; - public activeStep$: Observable<BescheidWizardStep>; - - public readonly bescheidWizardStep = BescheidWizardStep; - - ngOnInit(): void { - this.attachments$ = this.bescheidService.getAttachments(); - this.activeStep$ = this.bescheidService.getActiveStep(); - } - - public deleteAttachment(binaryFileResource: BinaryFileResource): void { - this.bescheidService.deleteAttachment(binaryFileResource); - } -} diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.html index 456fdbcb23a9cc1bca7ce775ccef70e8a544cf14..4f1ad6be37b4cce3aaca666cd2e5d41b3d25fbc9 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.html @@ -26,13 +26,20 @@ </ods-textarea-editor> </div> - <alfa-bescheid-wizard-dokumente-hochladen-summary - [isBescheidDocumentMissing]="false" - data-test-id="bescheid-versenden-dokumente" - ></alfa-bescheid-wizard-dokumente-hochladen-summary> + <alfa-bescheid-wizard-document-file-container data-test-id="bescheid-document-file"> + </alfa-bescheid-wizard-document-file-container> + @if (bescheidResource | hasLink: BescheidLinkRel.ATTACHMENTS) { + <alfa-binary-file-list-container + [resource]="bescheidResource" + [linkRel]="BescheidLinkRel.ATTACHMENTS" + [listOrientation]="BinaryFileListOrientation.VERTICAL" + data-test-id="bescheid-attachments" + ></alfa-binary-file-list-container> + } </div> + @if ( - (bescheidResource | hasLink: bescheidLinkRel.UPDATE) || (bescheidResource | hasLink: bescheidLinkRel.BESCHEIDEN_UND_SENDEN) + (bescheidResource | hasLink: BescheidLinkRel.UPDATE) || (bescheidResource | hasLink: BescheidLinkRel.BESCHEIDEN_UND_SENDEN) ) { <ods-button-with-spinner class="self-end" diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.spec.ts index e605449d8037e5d69d614a7a796060abb43a5c8f..3f2dd5b69457b1b28b56291e838c633fc17b120f 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.spec.ts @@ -1,21 +1,12 @@ import { BescheidLinkRel, BescheidWizardDialogResult } from '@alfa-client/bescheid-shared'; -import { CommandResource } from '@alfa-client/command-shared'; +import { BinaryFileListContainerComponent } from '@alfa-client/binary-file'; import { createEmptyStateResource, createErrorStateResource, createLoadingStateResource, HasLinkPipe, - StateResource, } from '@alfa-client/tech-shared'; -import { - existsAsHtmlElement, - getElementComponentFromFixtureByCss, - mock, - Mock, - notExistsAsHtmlElement, - triggerEvent, - useFromMock, -} from '@alfa-client/test-utils'; +import { existsAsHtmlElement, mock, Mock, notExistsAsHtmlElement, useFromMock } from '@alfa-client/test-utils'; import { DialogRef } from '@angular/cdk/dialog'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; @@ -26,19 +17,18 @@ import { EMPTY, of } from 'rxjs'; import { BescheidService2 } from '../../../../../../../../bescheid-shared/src/lib/bescheid2.service'; import { createBescheidResource, createWizard } from '../../../../../../../../bescheid-shared/src/test/bescheid'; import { createSuccessfullyDoneCommandStateResource } from '../../../../../../../../command-shared/test/command'; -import { getDataTestIdAttributeOf, getDataTestIdOf } from '../../../../../../../../tech-shared/test/data-test'; +import { getDataTestIdOf } from '../../../../../../../../tech-shared/test/data-test'; import { createProblemDetail } from '../../../../../../../../tech-shared/test/error'; import { singleColdCompleted } from '../../../../../../../../tech-shared/test/marbles'; import { BescheidFormService } from '../../../../bescheid.formservice'; -import { BescheidWizardDokumenteHochladenSummaryComponent } from '../../../dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component'; +import { BescheidWizardDocumentFileContainerComponent } from '../../../document-file-container/bescheid-wizard-document-file-container.component'; import { BescheidWizardBescheidVersendenSendenComponent } from './bescheid-wizard-bescheid-versenden-senden.component'; describe('BescheidWizardBescheidVersendenSendenComponent', () => { let component: BescheidWizardBescheidVersendenSendenComponent; let fixture: ComponentFixture<BescheidWizardBescheidVersendenSendenComponent>; - const dokumenteTestsId: string = getDataTestIdOf('bescheid-versenden-dokumente'); - const submitButtonTestId: string = getDataTestIdAttributeOf('send-button'); + const attachmentsTestId: string = getDataTestIdOf('bescheid-attachments'); const empfaengerTestId: string = getDataTestIdOf('bescheid-nachricht-empfaenger'); let bescheidService: Mock<BescheidService2>; @@ -56,10 +46,11 @@ describe('BescheidWizardBescheidVersendenSendenComponent', () => { await TestBed.configureTestingModule({ declarations: [ BescheidWizardBescheidVersendenSendenComponent, - MockComponent(BescheidWizardDokumenteHochladenSummaryComponent), MockComponent(ButtonWithSpinnerComponent), MockComponent(TextareaEditorComponent), MockComponent(TextEditorComponent), + MockComponent(BinaryFileListContainerComponent), + MockComponent(BescheidWizardDocumentFileContainerComponent), HasLinkPipe, ReactiveFormsModule, ], @@ -246,68 +237,21 @@ describe('BescheidWizardBescheidVersendenSendenComponent', () => { }); }); - describe('dokumente hochladen summary', () => { + describe('attachment list', () => { it('should exists', () => { - existsAsHtmlElement(fixture, dokumenteTestsId); - }); + component.bescheidResource = createBescheidResource([BescheidLinkRel.ATTACHMENTS]); - it('should have inputs', () => { - const dokumentComponent: BescheidWizardDokumenteHochladenSummaryComponent = - getElementComponentFromFixtureByCss<BescheidWizardDokumenteHochladenSummaryComponent>(fixture, dokumenteTestsId); + fixture.detectChanges(); - expect(dokumentComponent.isBescheidDocumentMissing).toBe(false); + existsAsHtmlElement(fixture, attachmentsTestId); }); - describe('submit button', () => { - it('should exists with update link', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.UPDATE]); - - fixture.detectChanges(); - - existsAsHtmlElement(fixture, dokumenteTestsId); - }); - - it('should exists with bescheiden link', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN_UND_SENDEN]); - - fixture.detectChanges(); - - existsAsHtmlElement(fixture, dokumenteTestsId); - }); + it('should NOT exists', () => { + component.bescheidResource = createBescheidResource(); - it('should NOT exists on missing links', () => { - component.bescheidResource = createBescheidResource(); - - fixture.detectChanges(); - - notExistsAsHtmlElement(fixture, submitButtonTestId); - }); - - it('should have inputs', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN_UND_SENDEN]); - const submitCommandStateResource: StateResource<CommandResource> = createSuccessfullyDoneCommandStateResource(); - component.submitStateResource$ = of(submitCommandStateResource); - - fixture.detectChanges(); - const submitButtonComponent: ButtonWithSpinnerComponent = - getElementComponentFromFixtureByCss<ButtonWithSpinnerComponent>(fixture, submitButtonTestId); - - expect(submitButtonComponent.stateResource).toEqual(submitCommandStateResource); - }); - - describe('output', () => { - describe('clickEmitter', () => { - it('should call handler', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN_UND_SENDEN]); - component.submit = jest.fn(); - fixture.detectChanges(); - - triggerEvent({ fixture, elementSelector: submitButtonTestId, name: 'clickEmitter' }); + fixture.detectChanges(); - expect(component.submit).toHaveBeenCalled(); - }); - }); - }); + notExistsAsHtmlElement(fixture, attachmentsTestId); }); }); }); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.ts index 5d03f994ed85918c979d7101e8d6b4ec14a59ee3..9f773a07f9008af8d653888cf198ed2a6b5ef11b 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/senden/bescheid-wizard-bescheid-versenden-senden.component.ts @@ -4,6 +4,7 @@ import { createEmptyStateResource, StateResource } from '@alfa-client/tech-share import { DialogRef } from '@angular/cdk/dialog'; import { Component, inject, Input } from '@angular/core'; import { Observable, of } from 'rxjs'; +import { BinaryFileListOrientation } from '../../../../../../../../binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive'; import { BescheidFormService } from '../../../../bescheid.formservice'; @Component({ @@ -22,8 +23,9 @@ export class BescheidWizardBescheidVersendenSendenComponent { public focusBetreff: boolean = false; public focusNachricht: boolean = false; - public readonly bescheidLinkRel = BescheidLinkRel; + public readonly BescheidLinkRel = BescheidLinkRel; public readonly formServiceClass = BescheidFormService; + public readonly BinaryFileListOrientation = BinaryFileListOrientation; public submit(): void { this._resetFocus(); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.html index 28a81da1c3c872131b56f1db53765a0608729194..7ab41e6c8a8e3b45c0f237d0de9eef96a4dfbd02 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.html @@ -1,14 +1,20 @@ <div class="flex h-full flex-col justify-between"> <div> <alfa-bescheid-wizard-antrag-bescheiden-summary></alfa-bescheid-wizard-antrag-bescheiden-summary> - <alfa-bescheid-wizard-dokumente-hochladen-summary - [isBescheidDocumentMissing]="false" - data-test-id="bescheid-versenden-dokumente" - ></alfa-bescheid-wizard-dokumente-hochladen-summary> + <alfa-bescheid-wizard-document-file-container data-test-id="bescheid-document-file"> + </alfa-bescheid-wizard-document-file-container> + @if (bescheidResource | hasLink: BescheidLinkRel.ATTACHMENTS) { + <alfa-binary-file-list-container + [resource]="bescheidResource" + [linkRel]="BescheidLinkRel.ATTACHMENTS" + [listOrientation]="BinaryFileListOrientation.VERTICAL" + data-test-id="bescheid-attachments" + ></alfa-binary-file-list-container> + } <p class="mb-8 text-base font-normal text-text">Der Bescheid muss manuell versendet werden.</p> </div> - @if ((bescheidResource | hasLink: bescheidLinkRel.UPDATE) || (bescheidResource | hasLink: bescheidLinkRel.BESCHEIDEN)) { + @if ((bescheidResource | hasLink: BescheidLinkRel.UPDATE) || (bescheidResource | hasLink: BescheidLinkRel.BESCHEIDEN)) { <ods-button-with-spinner class="self-end" [stateResource]="submitStateResource$ | async" diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.spec.ts index 40f0c44f14dc4e3e6c458f061f0c549cacd1763d..e73903700e781b9d36618c3a503f42395793fb21 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.spec.ts @@ -1,4 +1,5 @@ import { BescheidLinkRel, BescheidWizardDialogResult } from '@alfa-client/bescheid-shared'; +import { BinaryFileListContainerComponent } from '@alfa-client/binary-file'; import { CommandResource } from '@alfa-client/command-shared'; import { createEmptyStateResource, @@ -28,14 +29,14 @@ import { createProblemDetail } from '../../../../../../../../tech-shared/test/er import { singleColdCompleted } from '../../../../../../../../tech-shared/test/marbles'; import { BescheidFormService } from '../../../../bescheid.formservice'; import { BescheidWizardAntragBescheidenSummaryComponent } from '../../../antrag-bescheiden/summary/bescheid-wizard-antrag-bescheiden-summary.component'; -import { BescheidWizardDokumenteHochladenSummaryComponent } from '../../../dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component'; +import { BescheidWizardDocumentFileContainerComponent } from '../../../document-file-container/bescheid-wizard-document-file-container.component'; import { BescheidWizardBescheidVersendenSpeichernComponent } from './bescheid-wizard-bescheid-versenden-speichern.component'; describe('BescheidWizardBescheidVersendenSpeichernComponent', () => { let component: BescheidWizardBescheidVersendenSpeichernComponent; let fixture: ComponentFixture<BescheidWizardBescheidVersendenSpeichernComponent>; - const dokumenteTestsId: string = getDataTestIdOf('bescheid-versenden-dokumente'); + const attachmentsTestId: string = getDataTestIdOf('bescheid-attachments'); const submitButtonTestId: string = getDataTestIdAttributeOf('confirm-and-save-button'); let formService: Mock<BescheidFormService>; @@ -52,7 +53,8 @@ describe('BescheidWizardBescheidVersendenSpeichernComponent', () => { declarations: [ BescheidWizardBescheidVersendenSpeichernComponent, MockComponent(BescheidWizardAntragBescheidenSummaryComponent), - MockComponent(BescheidWizardDokumenteHochladenSummaryComponent), + MockComponent(BinaryFileListContainerComponent), + MockComponent(BescheidWizardDocumentFileContainerComponent), MockComponent(ButtonWithSpinnerComponent), HasLinkPipe, ], @@ -118,66 +120,73 @@ describe('BescheidWizardBescheidVersendenSpeichernComponent', () => { }); describe('template', () => { - describe('dokumente hochladen summary', () => { + describe('attachment list', () => { it('should exists', () => { - existsAsHtmlElement(fixture, dokumenteTestsId); + component.bescheidResource = createBescheidResource([BescheidLinkRel.ATTACHMENTS]); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, attachmentsTestId); }); - it('should have inputs', () => { - const dokumentComponent: BescheidWizardDokumenteHochladenSummaryComponent = - getElementComponentFromFixtureByCss<BescheidWizardDokumenteHochladenSummaryComponent>(fixture, dokumenteTestsId); + it('should NOT exists', () => { + component.bescheidResource = createBescheidResource(); + + fixture.detectChanges(); - expect(dokumentComponent.isBescheidDocumentMissing).toBe(false); + notExistsAsHtmlElement(fixture, attachmentsTestId); }); + }); - describe('submit button', () => { - it('should exists with update link', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.UPDATE]); + describe('submit button', () => { + it('should exists with update link', () => { + component.bescheidResource = createBescheidResource([BescheidLinkRel.UPDATE]); - fixture.detectChanges(); + fixture.detectChanges(); - existsAsHtmlElement(fixture, dokumenteTestsId); - }); + existsAsHtmlElement(fixture, submitButtonTestId); + }); - it('should exists with bescheiden link', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN]); + it('should exists with bescheiden link', () => { + component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN]); - fixture.detectChanges(); + fixture.detectChanges(); - existsAsHtmlElement(fixture, dokumenteTestsId); - }); + existsAsHtmlElement(fixture, submitButtonTestId); + }); - it('should NOT exists on missing links', () => { - component.bescheidResource = createBescheidResource(); + it('should NOT exists on missing links', () => { + component.bescheidResource = createBescheidResource(); - fixture.detectChanges(); + fixture.detectChanges(); - notExistsAsHtmlElement(fixture, submitButtonTestId); - }); + notExistsAsHtmlElement(fixture, submitButtonTestId); + }); - it('should have inputs', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN]); - const submitCommandStateResource: StateResource<CommandResource> = createSuccessfullyDoneCommandStateResource(); - component.submitStateResource$ = of(submitCommandStateResource); + it('should have inputs', () => { + component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN]); + const submitCommandStateResource: StateResource<CommandResource> = createSuccessfullyDoneCommandStateResource(); + component.submitStateResource$ = of(submitCommandStateResource); - fixture.detectChanges(); - const submitButtonComponent: ButtonWithSpinnerComponent = - getElementComponentFromFixtureByCss<ButtonWithSpinnerComponent>(fixture, submitButtonTestId); + fixture.detectChanges(); + const submitButtonComponent: ButtonWithSpinnerComponent = getElementComponentFromFixtureByCss<ButtonWithSpinnerComponent>( + fixture, + submitButtonTestId, + ); - expect(submitButtonComponent.stateResource).toEqual(submitCommandStateResource); - }); + expect(submitButtonComponent.stateResource).toEqual(submitCommandStateResource); + }); - describe('output', () => { - describe('clickEmitter', () => { - it('should call handler', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN]); - component.submit = jest.fn(); - fixture.detectChanges(); + describe('output', () => { + describe('clickEmitter', () => { + it('should call handler', () => { + component.bescheidResource = createBescheidResource([BescheidLinkRel.BESCHEIDEN]); + component.submit = jest.fn(); + fixture.detectChanges(); - triggerEvent({ fixture, elementSelector: submitButtonTestId, name: 'clickEmitter' }); + triggerEvent({ fixture, elementSelector: submitButtonTestId, name: 'clickEmitter' }); - expect(component.submit).toHaveBeenCalled(); - }); + expect(component.submit).toHaveBeenCalled(); }); }); }); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.ts index f7ee9b36506b3b783c70df4e5238eb690a7bb0c2..4f6bf47fb09a2a4e3653fa43f32cacd95962aff4 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/speichern/bescheid-wizard-bescheid-versenden-speichern.component.ts @@ -4,6 +4,7 @@ import { createEmptyStateResource, StateResource } from '@alfa-client/tech-share import { DialogRef } from '@angular/cdk/dialog'; import { Component, inject, Input } from '@angular/core'; import { Observable, of } from 'rxjs'; +import { BinaryFileListOrientation } from '../../../../../../../../binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive'; import { BescheidFormService } from '../../../../bescheid.formservice'; @Component({ @@ -18,7 +19,8 @@ export class BescheidWizardBescheidVersendenSpeichernComponent { public submitStateResource$: Observable<StateResource<CommandResource>> = of(createEmptyStateResource<CommandResource>()); - public readonly bescheidLinkRel = BescheidLinkRel; + public readonly BinaryFileListOrientation = BinaryFileListOrientation; + public readonly BescheidLinkRel = BescheidLinkRel; public submit(): void { this.submitStateResource$ = this.formService diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-wizard.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-wizard.component.html index 178a0f574555303c350994f4696c6e9e42f1fc52..9a8e66f72ed0f149c6b0be0d67562d5ca86ad5c6 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-wizard.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/bescheid-wizard.component.html @@ -31,7 +31,7 @@ ></ozgcloud-spinner> <form [formGroup]="formService.form" class="h-full"> - <div class="grid h-full"> + <div class="h-full"> @switch (activeStep) { @case (bescheidWizardStep.AntragBescheiden) { <alfa-bescheid-wizard-antrag-bescheiden-container diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/cancel-dialog-container/bescheid-wizard-cancel-dialog-container.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/cancel-dialog-container/bescheid-wizard-cancel-dialog-container.component.html index b9789fd188dee1479e529429092e9fefd574024a..9d885f59c02d12b386c35498c5a74c5a62f5ddea 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/cancel-dialog-container/bescheid-wizard-cancel-dialog-container.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/cancel-dialog-container/bescheid-wizard-cancel-dialog-container.component.html @@ -23,39 +23,32 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<div - class="relative m-6 max-w-2xl rounded-lg bg-modalBg p-6 shadow-xl" - data-test-id="bescheid-close-dialog" -> +<div class="relative m-6 max-w-2xl rounded-lg bg-modalBg p-6 shadow-xl" data-test-id="bescheid-close-dialog"> <div class="flex flex-col gap-6"> <div> <h4 class="text-lg font-medium text-primary">Bescheiderstellung abbrechen</h4> </div> <div class="grow"> - <p class="text-base"> - Soll der Bescheid-Entwurf zur späteren Bearbeitung gespeichert oder verworfen werden? - </p> + <p class="text-base">Soll der Bescheid-Entwurf zur späteren Bearbeitung gespeichert oder verworfen werden?</p> </div> <div class="flex gap-4"> - <ozgcloud-stroked-button-with-spinner + <ods-button-with-spinner [stateResource]="saveStateResource$ | async" (clickEmitter)="save()" - data-test-id="bescheiderstellung-abbrechen-entwurf-speichern" + dataTestId="bescheiderstellung-abbrechen-entwurf-speichern" text="Entwurf speichern" - type="submit" - icon="check" > - </ozgcloud-stroked-button-with-spinner> - <ozgcloud-stroked-button-with-spinner + <ods-check-icon class="fill-whitetext" icon></ods-check-icon> + </ods-button-with-spinner> + <ods-button-with-spinner [stateResource]="deleteStateResource$ | async" (clickEmitter)="cancel()" - data-test-id="bescheiderstellung-abbrechen-entwurf-verwerfen" + variant="outline" + dataTestId="bescheiderstellung-abbrechen-entwurf-verwerfen" text="Verwerfen" - color="" - icon="clear" - type="submit" > - </ozgcloud-stroked-button-with-spinner> + <ods-close-icon class="fill-primary" icon></ods-close-icon> + </ods-button-with-spinner> </div> </div> </div> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/cancel-dialog-container/bescheid-wizard-cancel-dialog-container.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/cancel-dialog-container/bescheid-wizard-cancel-dialog-container.component.spec.ts index 5dfde26e4da2a928be6046e7c14d50b60790900a..47aa453fd30472816900af6f9f3c15c74063a05d 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/cancel-dialog-container/bescheid-wizard-cancel-dialog-container.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/cancel-dialog-container/bescheid-wizard-cancel-dialog-container.component.spec.ts @@ -23,28 +23,40 @@ */ import { CommandResource } from '@alfa-client/command-shared'; import { createErrorStateResource, StateResource } from '@alfa-client/tech-shared'; -import { createDialogRefMock, DialogRefMock, existsAsHtmlElement, getElementComponentFromFixtureByCss, Mock, mock, triggerEvent, } from '@alfa-client/test-utils'; -import { OzgcloudStrokedButtonWithSpinnerComponent } from '@alfa-client/ui'; +import { + createDialogRefMock, + DialogRefMock, + existsAsHtmlElement, + getElementComponentFromFixtureByCss, + Mock, + mock, + triggerEvent, +} from '@alfa-client/test-utils'; import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { expect } from '@jest/globals'; +import { ButtonWithSpinnerComponent } from '@ods/component'; +import { CheckIconComponent, CloseIconComponent } from '@ods/system'; +import { BescheidService2 } from 'libs/bescheid-shared/src/lib/bescheid2.service'; +import { createBescheidResource } from 'libs/bescheid-shared/src/test/bescheid'; +import { createCommandStateResource, createSuccessfullyDoneCommandStateResource } from 'libs/command-shared/test/command'; +import { getDataTestIdAttributeOf } from 'libs/tech-shared/test/data-test'; +import { createApiError } from 'libs/tech-shared/test/error'; +import { singleColdCompleted } from 'libs/tech-shared/test/marbles'; import { MockComponent } from 'ng-mocks'; import { EMPTY, of } from 'rxjs'; -import { BescheidService2 } from '../../../../../../bescheid-shared/src/lib/bescheid2.service'; -import { createBescheidResource } from '../../../../../../bescheid-shared/src/test/bescheid'; -import { createCommandStateResource, createSuccessfullyDoneCommandStateResource, } from '../../../../../../command-shared/test/command'; -import { getDataTestIdOf } from '../../../../../../tech-shared/test/data-test'; -import { createApiError } from '../../../../../../tech-shared/test/error'; -import { singleColdCompleted } from '../../../../../../tech-shared/test/marbles'; import { BescheidFormService } from '../../bescheid.formservice'; -import { BescheidWizardCancelDialogContainerComponent, CancelWizardDialogData, } from './bescheid-wizard-cancel-dialog-container.component'; +import { + BescheidWizardCancelDialogContainerComponent, + CancelWizardDialogData, +} from './bescheid-wizard-cancel-dialog-container.component'; describe('BescheidWizardCancelDialogContainerComponent', () => { let component: BescheidWizardCancelDialogContainerComponent; let fixture: ComponentFixture<BescheidWizardCancelDialogContainerComponent>; - const speichernButton: string = getDataTestIdOf('bescheiderstellung-abbrechen-entwurf-speichern'); - const verwerfenButton: string = getDataTestIdOf('bescheiderstellung-abbrechen-entwurf-verwerfen'); + const speichernButton: string = getDataTestIdAttributeOf('bescheiderstellung-abbrechen-entwurf-speichern'); + const verwerfenButton: string = getDataTestIdAttributeOf('bescheiderstellung-abbrechen-entwurf-verwerfen'); const dialogData: CancelWizardDialogData = { bescheidResource: createBescheidResource(), @@ -64,7 +76,12 @@ describe('BescheidWizardCancelDialogContainerComponent', () => { async function configureTestingModule(dialogData: CancelWizardDialogData) { await TestBed.configureTestingModule({ - declarations: [BescheidWizardCancelDialogContainerComponent, MockComponent(OzgcloudStrokedButtonWithSpinnerComponent)], + declarations: [ + BescheidWizardCancelDialogContainerComponent, + MockComponent(ButtonWithSpinnerComponent), + MockComponent(CheckIconComponent), + MockComponent(CloseIconComponent), + ], providers: [ { provide: BescheidService2, @@ -233,10 +250,7 @@ describe('BescheidWizardCancelDialogContainerComponent', () => { const commandStateResource: StateResource<CommandResource> = createCommandStateResource(); component.saveStateResource$ = of(commandStateResource); - const elementComponent: OzgcloudStrokedButtonWithSpinnerComponent = getElementComponentFromFixtureByCss( - fixture, - speichernButton, - ); + const elementComponent: ButtonWithSpinnerComponent = getElementComponentFromFixtureByCss(fixture, speichernButton); fixture.detectChanges(); @@ -263,10 +277,7 @@ describe('BescheidWizardCancelDialogContainerComponent', () => { const commandStateResource: StateResource<CommandResource> = createCommandStateResource(); component.deleteStateResource$ = of(commandStateResource); - const elementComponent: OzgcloudStrokedButtonWithSpinnerComponent = getElementComponentFromFixtureByCss( - fixture, - verwerfenButton, - ); + const elementComponent: ButtonWithSpinnerComponent = getElementComponentFromFixtureByCss(fixture, verwerfenButton); fixture.detectChanges(); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.html index db9307187ec8c100d91ff2c2ffc7b99dd22fc5e2..4b6a5edec76e6372f0bc4f2665b8fccb8bd4e637 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.html @@ -1,12 +1,10 @@ -@if (bescheidResource | hasLink: BescheidLinkRel.CREATE_DOCUMENT) { - <ods-button-card - class="w-full max-w-72" - [isLoading]="bescheidDocument.create.loading" - (click)="clickEmitter.emit()" - text="Bescheiddokument" - subText="automatisch erstellen" - data-test-id="create-bescheid-document-button" - > - <ods-bescheid-generate-icon icon /> - </ods-button-card> -} +<ods-button-card + class="w-full max-w-72" + [isLoading]="bescheidDocument.create.loading" + (click)="clickEmitter.emit()" + text="Bescheiddokument" + subText="automatisch erstellen" + data-test-id="create-bescheid-document-button" +> + <ods-bescheid-generate-icon icon /> +</ods-button-card> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.spec.ts index 0870d4c34bd3f5dab6ae510dfe171877bc700590..9473e81b5c48467f07cead6babee0a53cfb51a21 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.spec.ts @@ -1,13 +1,6 @@ import { BescheidDocument, BescheidLinkRel, createEmptyBescheidDocument } from '@alfa-client/bescheid-shared'; import { HasLinkPipe } from '@alfa-client/tech-shared'; -import { - existsAsHtmlElement, - getElementComponentFromFixtureByCss, - mock, - Mock, - notExistsAsHtmlElement, - triggerEvent, -} from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getElementComponentFromFixtureByCss, mock, Mock, triggerEvent } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BescheidGenerateIconComponent, ButtonCardComponent } from '@ods/system'; import { MockComponent } from 'ng-mocks'; @@ -73,21 +66,11 @@ describe('BescheidWizardAutomatischErstellenButtonComponent', () => { describe('template', () => { describe('create button', () => { it('should exists', () => { - component.bescheidResource = createBescheidResource([BescheidLinkRel.CREATE_DOCUMENT]); - fixture.detectChanges(); existsAsHtmlElement(fixture, createButtonTestId); }); - it('should NOT exists', () => { - component.bescheidResource = createBescheidResource(); - - fixture.detectChanges(); - - notExistsAsHtmlElement(fixture, createButtonTestId); - }); - it('should have been called with inputs', () => { component.bescheidResource = createBescheidResource([BescheidLinkRel.CREATE_DOCUMENT]); component.bescheidDocument = bescheidDocument; diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.ts index 083aec95ac2ddd1d4b030e8ab57a3baf6b55f72e..5f268e460848a0e76c756106f1ead4a4fcee10fb 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/create-document-button-container/create-document-button/bescheid-wizard-create-document-button.component.ts @@ -8,7 +8,6 @@ import { BescheidFormService } from '../../../bescheid.formservice'; }) export class BescheidWizardCreateDocumentButtonComponent { @Input() bescheidResource: BescheidResource; - @Input() set bescheidDocument(value: BescheidDocument) { this._bescheidDocument = value; this.formService.updateBescheidDocumentFile(value.documentUri); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/bescheid-wizard-dokumente-hochladen-container.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/bescheid-wizard-dokumente-hochladen-container.component.spec.ts index 749ab02b1417d171af7d7549c1c2994d4116fc04..26fb71e977b88813e501e3adcebe112e4077c3fb 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/bescheid-wizard-dokumente-hochladen-container.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/bescheid-wizard-dokumente-hochladen-container.component.spec.ts @@ -112,12 +112,6 @@ describe('BescheidWizardDokumenteHochladenComponent', () => { }); describe('onWeiterClick', () => { - it('should finish adding attachments', () => { - component.onWeiterClick(); - - expect(bescheidService.finishAddingAttachments).toHaveBeenCalled(); - }); - it('should finish adding bescheid document', () => { component.onWeiterClick(); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/bescheid-wizard-dokumente-hochladen-container.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/bescheid-wizard-dokumente-hochladen-container.component.ts index eb90a6109b54253184476b5b3542bd31f0851d94..c15d92820a7e08b122ed18f729dc344adc8f8a58 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/bescheid-wizard-dokumente-hochladen-container.component.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/bescheid-wizard-dokumente-hochladen-container.component.ts @@ -45,7 +45,6 @@ export class BescheidWizardDokumenteHochladenContainerComponent implements OnIni } public onWeiterClick(): void { - this.bescheidService.finishAddingAttachments(); this.bescheidService.finishAddingBescheidDocument(); this.bescheidService.setActiveStep(BescheidWizardStep.BescheidVersenden); } diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.html index fd2a2ab6c43b6e00ba06a0347278e2ab53b0c5aa..007a02ec854d9001c4eaef88f688760d6db3fa8d 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.html @@ -23,24 +23,30 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<div class="mt-4 flex flex-col gap-4"> - <alfa-bescheid-wizard-create-document-button-container - [bescheidResource]="bescheidResource" - data-test-id="create-document-button" - ></alfa-bescheid-wizard-create-document-button-container> - <alfa-bescheid-wizard-upload-document-button-container - [bescheidResource]="bescheidResource" - data-test-id="upload-document-button" - ></alfa-bescheid-wizard-upload-document-button-container> - <alfa-bescheid-wizard-upload-attachment-button-container - [bescheidResource]="bescheidResource" - data-test-id="upload-attachment-button" - ></alfa-bescheid-wizard-upload-attachment-button-container> - @if (bescheidResource | hasLink: bescheidLinkRel.UPDATE) { +<div class="mt-4 flex max-w-72 flex-col gap-4"> + @if (bescheidResource | hasLink: BescheidLinkRel.CREATE_DOCUMENT) { + <alfa-bescheid-wizard-create-document-button-container + [bescheidResource]="bescheidResource" + data-test-id="create-document-button" + /> + } + @if (bescheidResource | hasLink: BescheidLinkRel.UPLOAD_BESCHEID_FILE) { + <alfa-bescheid-wizard-upload-document-button-container + [bescheidResource]="bescheidResource" + data-test-id="upload-document-button" + /> + } + @if (bescheidResource | hasLink: BescheidLinkRel.UPLOAD_ATTACHMENT) { + <alfa-bescheid-wizard-upload-attachment-button-container + [bescheidResource]="bescheidResource" + data-test-id="upload-attachment-button" + /> + } + @if (bescheidResource | hasLink: BescheidLinkRel.UPDATE) { <alfa-bescheid-wizard-weiter-button [submitStateResource]="submitStateResource$ | async" (clickEmitter)="gotoNextStep()" data-test-id="weiter-button" - ></alfa-bescheid-wizard-weiter-button> + /> } </div> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.spec.ts index 02452fe302b69a044cef2cf621ea5a1e5734d6c2..57df8329cac9e5097d8045cd8f1448fdcb9d0f2b 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.spec.ts @@ -21,10 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { BescheidDocument, BescheidLinkRel, createEmptyBescheidDocument } from '@alfa-client/bescheid-shared'; +import { BescheidDocument, BescheidLinkRel, BescheidResource, createEmptyBescheidDocument } from '@alfa-client/bescheid-shared'; import { CommandResource } from '@alfa-client/command-shared'; import { HasLinkPipe, StateResource } from '@alfa-client/tech-shared'; -import { existsAsHtmlElement, getElementComponentFromFixtureByCss, mock, Mock, notExistsAsHtmlElement, triggerEvent, } from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getMockComponent, mock, Mock, notExistsAsHtmlElement, triggerEvent } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockComponent } from 'ng-mocks'; import { EMPTY, of } from 'rxjs'; @@ -129,59 +129,98 @@ describe('BescheidWizardDokumenteHochladenFormComponent', () => { describe('template', () => { describe('create document button', () => { - it('should exists', () => { + const bescheidResourceWithLink: BescheidResource = createBescheidResource([BescheidLinkRel.CREATE_DOCUMENT]); + + it('should be visible if link is present', () => { + component.bescheidResource = bescheidResourceWithLink; + + fixture.detectChanges(); + existsAsHtmlElement(fixture, createDocumentButtonTestId); }); - it('should have been called with inputs', () => { + it('should hide if link is missing', () => { component.bescheidResource = createBescheidResource(); fixture.detectChanges(); - const createDocumentButton: BescheidWizardCreateDocumentButtonContainerComponent = - getElementComponentFromFixtureByCss<BescheidWizardCreateDocumentButtonContainerComponent>( - fixture, - createDocumentButtonTestId, - ); - expect(createDocumentButton.bescheidResource).toEqual(component.bescheidResource); + notExistsAsHtmlElement(fixture, createDocumentButtonTestId); + }); + + it('should have been called with inputs', () => { + component.bescheidResource = bescheidResourceWithLink; + + fixture.detectChanges(); + + const createDocumentButtonComp: BescheidWizardCreateDocumentButtonContainerComponent = getMockComponent( + fixture, + BescheidWizardCreateDocumentButtonContainerComponent, + ); + expect(createDocumentButtonComp.bescheidResource).toEqual(bescheidResourceWithLink); }); }); describe('upload document button', () => { - it('should exists', () => { + const bescheidResourceWithLink: BescheidResource = createBescheidResource([BescheidLinkRel.UPLOAD_BESCHEID_FILE]); + + it('should be visible if link is present', () => { + component.bescheidResource = bescheidResourceWithLink; + + fixture.detectChanges(); + existsAsHtmlElement(fixture, uploadDocumentButtonTestId); }); - it('should have been called with inputs', () => { + it('should hide if link is missing', () => { component.bescheidResource = createBescheidResource(); fixture.detectChanges(); - const uploadDocumentButton: BescheidWizardUploadDocumentButtonContainerComponent = - getElementComponentFromFixtureByCss<BescheidWizardUploadDocumentButtonContainerComponent>( - fixture, - uploadDocumentButtonTestId, - ); - expect(uploadDocumentButton.bescheidResource).toEqual(component.bescheidResource); + notExistsAsHtmlElement(fixture, uploadDocumentButtonTestId); + }); + + it('should have been called with inputs', () => { + component.bescheidResource = bescheidResourceWithLink; + + fixture.detectChanges(); + + const uploadDocumentButtonComp: BescheidWizardUploadDocumentButtonContainerComponent = getMockComponent( + fixture, + BescheidWizardUploadDocumentButtonContainerComponent, + ); + expect(uploadDocumentButtonComp.bescheidResource).toEqual(bescheidResourceWithLink); }); }); describe('upload attachment button', () => { - it('should exists', () => { + const bescheidResourceWithLink: BescheidResource = createBescheidResource([BescheidLinkRel.UPLOAD_ATTACHMENT]); + + it('should be visible if link is present', () => { + component.bescheidResource = bescheidResourceWithLink; + + fixture.detectChanges(); + existsAsHtmlElement(fixture, uploadAttachmentButtonTestId); }); - it('should have been called with inputs', () => { + it('should hide if link is missing', () => { component.bescheidResource = createBescheidResource(); fixture.detectChanges(); - const uploadAttachmentButton: BescheidWizardUploadAttachmentButtonContainerComponent = - getElementComponentFromFixtureByCss<BescheidWizardUploadAttachmentButtonContainerComponent>( - fixture, - uploadDocumentButtonTestId, - ); - expect(uploadAttachmentButton.bescheidResource).toEqual(component.bescheidResource); + notExistsAsHtmlElement(fixture, uploadAttachmentButtonTestId); + }); + + it('should have been called with inputs', () => { + component.bescheidResource = bescheidResourceWithLink; + + fixture.detectChanges(); + + const uploadAttachmentButtonComp: BescheidWizardUploadAttachmentButtonContainerComponent = getMockComponent( + fixture, + BescheidWizardUploadAttachmentButtonContainerComponent, + ); + expect(uploadAttachmentButtonComp.bescheidResource).toEqual(bescheidResourceWithLink); }); }); @@ -208,11 +247,12 @@ describe('BescheidWizardDokumenteHochladenFormComponent', () => { component.bescheidResource = createBescheidResource([BescheidLinkRel.UPDATE]); fixture.detectChanges(); - const weiterButton: BescheidWizardWeiterButtonComponent = - getElementComponentFromFixtureByCss<BescheidWizardWeiterButtonComponent>(fixture, weiterButtonTestId); - component.submitStateResource$.subscribe(); - expect(weiterButton.submitStateResource).toEqual(commandStateResource); + const weiterButtonComp: BescheidWizardWeiterButtonComponent = getMockComponent( + fixture, + BescheidWizardWeiterButtonComponent, + ); + expect(weiterButtonComp.submitStateResource).toEqual(commandStateResource); }); describe('output', () => { diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.ts index d09a24bce3dfd8a65dac3470147f42fff00aeda9..0aafb95573f07d4e36e93e6cb238671cef610816 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/form/bescheid-wizard-dokumente-hochladen-form.component.ts @@ -53,7 +53,7 @@ export class BescheidWizardDokumenteHochladenFormComponent { private _bescheidDocument: BescheidDocument; - protected readonly bescheidLinkRel = BescheidLinkRel; + protected readonly BescheidLinkRel = BescheidLinkRel; public gotoNextStep(): void { if (isNil(this._bescheidDocument.resource)) { diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.html index b8c80fafbcec28185f7ff568c8b5357c197b1503..ecfcc883fa7680734dbf7dacb9c81fb197f6c5de 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.html @@ -34,6 +34,12 @@ </alfa-bescheid-wizard-document-file-container> </div> <div class="my-4"> - <alfa-bescheid-wizard-attachment-files-container data-test-id="bescheid-attachment-files"> - </alfa-bescheid-wizard-attachment-files-container> + <ods-file-upload-list-container + [parentFormArrayName]="BescheidFormService.FIELD_ATTACHMENTS" + [fileUploadType]="BESCHEID_UPLOADED_ATTACHMENTS" + [filesResource]="bescheidResource" + [filesLinkRel]="BescheidLinkRel.ATTACHMENTS" + [listOrientation]="BinaryFileListOrientation.VERTICAL" + data-test-id="bescheid-attachments" + ></ods-file-upload-list-container> </div> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.spec.ts index 5ed6c9e5d11f1e5ebbe9fda0b395d70bc856c323..dd6eaf2ac287739ddbc00314a685b410f6740151 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.spec.ts @@ -21,11 +21,11 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { FileUploadListContainerComponent } from '@alfa-client/binary-file'; import { existsAsHtmlElement, notExistsAsHtmlElement } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockComponent } from 'ng-mocks'; import { getDataTestIdOf } from '../../../../../../../tech-shared/test/data-test'; -import { BescheidWizardAttachmentFilesContainerComponent } from '../../attachment-files-container/bescheid-wizard-attachment-files-container.component'; import { BescheidWizardDocumentFileContainerComponent } from '../../document-file-container/bescheid-wizard-document-file-container.component'; import { BescheidWizardDokumenteHochladenSummaryComponent } from './bescheid-wizard-dokumente-hochladen-summary.component'; @@ -35,14 +35,14 @@ describe('BescheidWizardDokumenteHochladenSummaryComponent', () => { const missingBescheidErrorMessageTestId: string = getDataTestIdOf('missing-bescheid-document-error-message'); const documentFileTestId: string = getDataTestIdOf('bescheid-document-file'); - const attachmentFilesTestId: string = getDataTestIdOf('bescheid-attachment-files'); + const attachmentFilesTestId: string = getDataTestIdOf('bescheid-attachments'); beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ BescheidWizardDokumenteHochladenSummaryComponent, MockComponent(BescheidWizardDocumentFileContainerComponent), - MockComponent(BescheidWizardAttachmentFilesContainerComponent), + MockComponent(FileUploadListContainerComponent), ], }).compileComponents(); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.ts index 6bf136b2492c45205aa1fe115c3a033d92c1d174..7337d6cc572a2218be84e2c81b7c4da8f3109d86 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/dokumente-hochladen-container/summary/bescheid-wizard-dokumente-hochladen-summary.component.ts @@ -21,12 +21,21 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { BESCHEID_UPLOADED_ATTACHMENTS, BescheidLinkRel, BescheidResource } from '@alfa-client/bescheid-shared'; import { Component, Input } from '@angular/core'; +import { BinaryFileListOrientation } from '../../../../../../../binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive'; +import { BescheidFormService } from '../../../bescheid.formservice'; @Component({ selector: 'alfa-bescheid-wizard-dokumente-hochladen-summary', templateUrl: './bescheid-wizard-dokumente-hochladen-summary.component.html', }) export class BescheidWizardDokumenteHochladenSummaryComponent { + @Input() bescheidResource: BescheidResource; @Input() isBescheidDocumentMissing: boolean; + + public readonly BESCHEID_UPLOADED_ATTACHMENTS = BESCHEID_UPLOADED_ATTACHMENTS; + public readonly BescheidLinkRel = BescheidLinkRel; + public readonly BescheidFormService = BescheidFormService; + public readonly BinaryFileListOrientation = BinaryFileListOrientation; } diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/step-content-layout/step-content-layout.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/step-content-layout/step-content-layout.component.html index 43b8946ab2d59886a58ed927028c68158785dedc..5151ccc66814fe01ce9a81566ef11cdb0d4f415b 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/step-content-layout/step-content-layout.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/step-content-layout/step-content-layout.component.html @@ -24,17 +24,17 @@ --> <div class="flex h-full gap-11"> - <div class="flex w-1/2 flex-row gap-7"> + <div class="flex flex-1 flex-row gap-7 overflow-y-auto"> <alfa-bescheid-wizard-stepper [activeStep]="activeStep" (stepChange)="stepChange.emit($event)" data-test-id="wizard-stepper" /> - <div class="mt-2 flex flex-1 flex-col"> + <div class="mr-2 mt-2 flex flex-1 flex-col"> <ng-content select="[stepPanel]" /> </div> </div> - <div class="flex w-1/2"> + <div class="flex flex-1"> <alfa-bescheid-wizard-summary headline="Bescheid" data-test-id="wizard-summary"> <ng-content select="[summary]" /> </alfa-bescheid-wizard-summary> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.html index 0442de58d2afd83f13f0adb7f928829302ffcea2..c81df29d6b8b5c614834ca40e8fd419ad24088cc 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.html @@ -1,5 +1,7 @@ -<alfa-bescheid-wizard-upload-attachment-button - [attachments]="attachments$ | async" - (uploadFile)="uploadFile($event)" +<ods-multi-file-upload-editor + label="Anhang hochladen" + [fileUploadType]="BESCHEID_UPLOADED_ATTACHMENTS" + [uploadResource]="bescheidResource" + [uploadLinkRelation]="BescheidLinkRel.UPLOAD_ATTACHMENT" data-test-id="upload-attachment-button" -></alfa-bescheid-wizard-upload-attachment-button> +></ods-multi-file-upload-editor> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.spec.ts index 6035095eb818710798ad643934245f0ad1cf503f..4cbffd3fd1f894ec6b10e64f579b8021e6c86d72 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.spec.ts @@ -1,37 +1,15 @@ -import { BescheidAttachments, BescheidResource } from '@alfa-client/bescheid-shared'; -import { getElementComponentFromFixtureByCss, Mock, mock, triggerEvent } from '@alfa-client/test-utils'; +import { MultiFileUploadEditorComponent } from '@alfa-client/binary-file'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; -import { BescheidService2 } from '../../../../../../bescheid-shared/src/lib/bescheid2.service'; -import { createBescheidAttachments, createBescheidResource } from '../../../../../../bescheid-shared/src/test/bescheid'; -import { getDataTestIdOf } from '../../../../../../tech-shared/test/data-test'; -import { createFile } from '../../../../../../tech-shared/test/file'; -import { singleColdCompleted } from '../../../../../../tech-shared/test/marbles'; import { BescheidWizardUploadAttachmentButtonContainerComponent } from './bescheid-wizard-upload-attachment-button-container.component'; -import { BescheidWizardUploadAttachmentButtonComponent } from './upload-attachment-button/bescheid-wizard-upload-attachment-button.component'; describe('BescheidWizardUploadAttachmentButtonContainerComponent', () => { let component: BescheidWizardUploadAttachmentButtonContainerComponent; let fixture: ComponentFixture<BescheidWizardUploadAttachmentButtonContainerComponent>; - const uploadAttachmentButtonTestId: string = getDataTestIdOf('upload-attachment-button'); - - const bescheidAttachments: BescheidAttachments = createBescheidAttachments(); - - let bescheidService: Mock<BescheidService2>; - - beforeEach(() => { - bescheidService = mock(BescheidService2); - }); - beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - BescheidWizardUploadAttachmentButtonContainerComponent, - MockComponent(BescheidWizardUploadAttachmentButtonComponent), - ], - providers: [{ provide: BescheidService2, useValue: bescheidService }], + declarations: [BescheidWizardUploadAttachmentButtonContainerComponent, MockComponent(MultiFileUploadEditorComponent)], }).compileComponents(); createComponent(); @@ -43,59 +21,7 @@ describe('BescheidWizardUploadAttachmentButtonContainerComponent', () => { fixture.detectChanges(); } - describe('component', () => { - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should set initial values', () => { - bescheidService.getAttachments.mockReturnValue(of(bescheidAttachments)); - - createComponent(); - - expect(component.attachments$).toBeObservable(singleColdCompleted(bescheidAttachments)); - }); - - describe('uploadFile', () => { - it('should call bescheid service', () => { - const bescheidResource: BescheidResource = createBescheidResource(); - const file: File = createFile(); - component.bescheidResource = bescheidResource; - - component.uploadFile(file); - - expect(bescheidService.uploadAttachment).toHaveBeenCalledWith(file, bescheidResource); - }); - }); - }); - - describe('template', () => { - describe('upload attachment button', () => { - it('should have been called with inputs', () => { - bescheidService.getAttachments.mockReturnValue(of(bescheidAttachments)); - createComponent(); - - const uploadAttachmentButtonComponent: BescheidWizardUploadAttachmentButtonComponent = - getElementComponentFromFixtureByCss<BescheidWizardUploadAttachmentButtonComponent>( - fixture, - uploadAttachmentButtonTestId, - ); - - expect(uploadAttachmentButtonComponent.attachments).toEqual(bescheidAttachments); - }); - - describe('output', () => { - describe('uploadFile', () => { - it('should call handler', () => { - component.uploadFile = jest.fn(); - const file: File = createFile(); - - triggerEvent({ fixture, elementSelector: uploadAttachmentButtonTestId, name: 'uploadFile', data: file }); - - expect(component.uploadFile).toHaveBeenCalledWith(file); - }); - }); - }); - }); + it('should create', () => { + expect(component).toBeTruthy(); }); }); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.ts index 620adb8db26f04e7dc6b810c37c6acdef0c12d4e..527ce7e4280a9523aa396e918cac1436441f685e 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component.ts @@ -1,7 +1,5 @@ -import { BescheidAttachments, BescheidResource } from '@alfa-client/bescheid-shared'; -import { Component, inject, Input } from '@angular/core'; -import { Observable } from 'rxjs'; -import { BescheidService2 } from '../../../../../../bescheid-shared/src/lib/bescheid2.service'; +import { BESCHEID_UPLOADED_ATTACHMENTS, BescheidLinkRel, BescheidResource } from '@alfa-client/bescheid-shared'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'alfa-bescheid-wizard-upload-attachment-button-container', @@ -10,11 +8,6 @@ import { BescheidService2 } from '../../../../../../bescheid-shared/src/lib/besc export class BescheidWizardUploadAttachmentButtonContainerComponent { @Input() bescheidResource: BescheidResource; - private readonly bescheidService = inject(BescheidService2); - - public readonly attachments$: Observable<BescheidAttachments> = this.bescheidService.getAttachments(); - - public uploadFile(file: File): void { - this.bescheidService.uploadAttachment(file, this.bescheidResource); - } + public readonly BESCHEID_UPLOADED_ATTACHMENTS = BESCHEID_UPLOADED_ATTACHMENTS; + public readonly BescheidLinkRel = BescheidLinkRel; } diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.html deleted file mode 100644 index daa2be54c067aee56f12918fc97895d4a9cfd87f..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.html +++ /dev/null @@ -1,11 +0,0 @@ -<div [formGroup]="formService.form"> - <ods-file-upload-editor - [parentFormArrayName]="formServiceClass.FIELD_ATTACHMENTS" - [uploadInProgress]="upload" - [fileLinkList]="fileUrls" - (newFile)="uploadFile.emit($event)" - label="Anhang hochladen" - data-test-id="bescheid-wizard-upload-editor" - > - </ods-file-upload-editor> -</div> diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.spec.ts deleted file mode 100644 index 7aff85b5a346f756fae78c33398f875236e24390..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { BescheidAttachments } from '@alfa-client/bescheid-shared'; -import { createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared'; -import { getElementComponentFromFixtureByCss, triggerEvent } from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; -import { faker } from '@faker-js/faker'; -import { FileUploadEditorComponent } from '@ods/component'; -import { MockComponent } from 'ng-mocks'; -import { createBescheidAttachments } from '../../../../../../../bescheid-shared/src/test/bescheid'; -import { createBinaryFileResource } from '../../../../../../../binary-file-shared/test/binary-file'; -import { getDataTestIdOf } from '../../../../../../../tech-shared/test/data-test'; -import { createFile } from '../../../../../../../tech-shared/test/file'; -import { BescheidFormService } from '../../../bescheid.formservice'; -import { BescheidWizardUploadAttachmentButtonComponent } from './bescheid-wizard-upload-attachment-button.component'; - -describe('BescheidWizardUploadAttachmentButtonComponent', () => { - let component: BescheidWizardUploadAttachmentButtonComponent; - let fixture: ComponentFixture<BescheidWizardUploadAttachmentButtonComponent>; - - const uploadEditorTestId: string = getDataTestIdOf('bescheid-wizard-upload-editor'); - - const bescheidAttachments: BescheidAttachments = createBescheidAttachments(); - - let formService: BescheidFormService; - - beforeEach(() => { - formService = new BescheidFormService(new FormBuilder(), null); - }); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [BescheidWizardUploadAttachmentButtonComponent, MockComponent(FileUploadEditorComponent)], - providers: [{ provide: BescheidFormService, useValue: formService }], - imports: [ReactiveFormsModule], - }).compileComponents(); - - fixture = TestBed.createComponent(BescheidWizardUploadAttachmentButtonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - describe('component', () => { - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should set initial values', () => { - expect(component.upload).toEqual(createEmptyStateResource()); - expect(component.fileUrls).toEqual([]); - }); - }); - - describe('template', () => { - describe('upload editor', () => { - it('should have been called with inputs', () => { - component.upload = createStateResource(createBinaryFileResource()); - component.fileUrls = [faker.internet.url()]; - - fixture.detectChanges(); - const uploadEditorComponent: FileUploadEditorComponent = getElementComponentFromFixtureByCss<FileUploadEditorComponent>( - fixture, - uploadEditorTestId, - ); - - expect(uploadEditorComponent.parentFormArrayName).toEqual(BescheidFormService.FIELD_ATTACHMENTS); - expect(uploadEditorComponent.uploadInProgress).toEqual(component.upload); - expect(uploadEditorComponent.fileLinkList).toEqual(component.fileUrls); - }); - - describe('output', () => { - describe('newFile', () => { - it('should emit', () => { - component.uploadFile.emit = jest.fn(); - const file: File = createFile(); - - triggerEvent({ fixture, elementSelector: uploadEditorTestId, name: 'newFile', data: file }); - - expect(component.uploadFile.emit).toHaveBeenCalledWith(file); - }); - }); - }); - }); - }); -}); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.ts deleted file mode 100644 index 0ec17648a4f94e19a0b6184e4a2cbb68f1b6edcd..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BescheidAttachments, createEmptyBescheidAttachments } from '@alfa-client/bescheid-shared'; -import { BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; -import { Component, EventEmitter, inject, Input, Output } from '@angular/core'; -import { getUrl } from '@ngxp/rest'; -import { BescheidFormService } from '../../../bescheid.formservice'; - -@Component({ - selector: 'alfa-bescheid-wizard-upload-attachment-button', - templateUrl: './bescheid-wizard-upload-attachment-button.component.html', -}) -export class BescheidWizardUploadAttachmentButtonComponent { - @Input() set attachments(value: BescheidAttachments) { - this.updateAttachments(value); - } - - @Output() uploadFile: EventEmitter<File> = new EventEmitter<File>(); - - public readonly formService: BescheidFormService = inject(BescheidFormService); - - public fileUrls: string[] = []; - public upload: StateResource<BinaryFileResource> = createEmptyStateResource(); - private _attachments: BescheidAttachments = createEmptyBescheidAttachments(); - - public readonly formServiceClass = BescheidFormService; - - updateAttachments(value: BescheidAttachments) { - this._attachments = value; - this.upload = value.uploadStateResource; - this.fileUrls = this._attachments.items.map((binaryFileResource: BinaryFileResource) => getUrl(binaryFileResource)); - } -} diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component.html b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component.html index 0b04ceb6fc50d6d98b70f95f7f252c6a62a11252..6ddb97a03c2c07a4e314f33c6d7d8297e4087eca 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component.html +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component.html @@ -1,6 +1,5 @@ <div [formGroup]="formService.form" class="w-full"> <ods-single-file-upload-editor - *ngIf="bescheidResource | hasLink: bescheidLinkRel.UPLOAD_BESCHEID_FILE" [uploadInProgress]="upload.loading" [formControlName]="formServiceClass.FIELD_BESCHEID_DOCUMENT" (newFile)="uploadFile($event)" diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component.spec.ts index d6616d37317a467e6a8c1976cfb1f2f5fc937ffc..66689e97b55fe62c4176df10614f23737020c5ba 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component.spec.ts @@ -4,7 +4,6 @@ import { existsAsHtmlElement, getElementComponentFromFixtureByCss, mock, - notExistsAsHtmlElement, triggerEvent, useFromMock, } from '@alfa-client/test-utils'; @@ -110,14 +109,6 @@ describe('BescheidWizardDokumentHochladenButtonComponent', () => { existsAsHtmlElement(fixture, fileUploadEditorTestId); }); - it('should NOT exists', () => { - component.bescheidResource = createBescheidResource(); - - fixture.detectChanges(); - - notExistsAsHtmlElement(fixture, fileUploadEditorTestId); - }); - it('should have been called with inputs', () => { component.bescheidResource = createBescheidResource([BescheidLinkRel.UPLOAD_BESCHEID_FILE]); component.upload = createPendingUpload(); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid.formservice.spec.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid.formservice.spec.ts index 2125d0b8e171796f264901d4b22e40c269d0cc7d..86c5f0caee0f27416b51343699e465ec2eb4ef47 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid.formservice.spec.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid.formservice.spec.ts @@ -415,41 +415,41 @@ describe('BescheidFormService', () => { }); describe('patchValues', () => { + const sendBy: BescheidSendBy = BescheidSendBy.NACHRICHT; let bescheidResource: BescheidResource; let patch: jest.Mock; beforeEach(() => { bescheidResource = createBescheidResource(); patch = service.patch = jest.fn(); + service._evaluateSendBy = jest.fn().mockReturnValue(sendBy); }); it('should patch', () => { + const sendBy: BescheidSendBy = BescheidSendBy.NACHRICHT; service.patchValues(bescheidResource); expect(patch).toHaveBeenCalledWith({ [BescheidFormService.FIELD_BESCHIEDEN_AM]: bescheidResource.beschiedenAm, [BescheidFormService.FIELD_BEWILLIGT]: String(bescheidResource.bewilligt), [BescheidFormService.FIELD_BESCHEID_DOCUMENT]: null, - [BescheidFormService.FIELD_SEND_BY]: String(bescheidResource.sendBy), + [BescheidFormService.FIELD_SEND_BY]: String(sendBy), [BescheidFormService.FIELD_NACHRICHT_SUBJECT]: bescheidResource.nachrichtSubject, [BescheidFormService.FIELD_NACHRICHT_TEXT]: bescheidResource.nachrichtText, }); }); - it('should patch sendBy to default', () => { - service.patchValues({ - ...bescheidResource, - sendBy: undefined, - }); + it('should set send by', () => { + const sendBy: BescheidSendBy = BescheidSendBy.NACHRICHT; + service.patchValues(bescheidResource); - expect(patch).toHaveBeenCalledWith({ - [BescheidFormService.FIELD_BESCHIEDEN_AM]: bescheidResource.beschiedenAm, - [BescheidFormService.FIELD_BEWILLIGT]: String(bescheidResource.bewilligt), - [BescheidFormService.FIELD_SEND_BY]: BescheidSendBy.NACHRICHT, - [BescheidFormService.FIELD_BESCHEID_DOCUMENT]: null, - [BescheidFormService.FIELD_NACHRICHT_SUBJECT]: bescheidResource.nachrichtSubject, - [BescheidFormService.FIELD_NACHRICHT_TEXT]: bescheidResource.nachrichtText, - }); + expect(bescheidService.setSendBy).toHaveBeenCalledWith(sendBy); + }); + + it('should evaluate send by', () => { + service.patchValues(bescheidResource); + + expect(service._evaluateSendBy).toHaveBeenCalledWith(bescheidResource); }); it('should patch bescheid document uri', () => { @@ -465,7 +465,7 @@ describe('BescheidFormService', () => { expect(patch).toHaveBeenCalledWith({ [BescheidFormService.FIELD_BESCHIEDEN_AM]: bescheidResource.beschiedenAm, [BescheidFormService.FIELD_BEWILLIGT]: String(bescheidResource.bewilligt), - [BescheidFormService.FIELD_SEND_BY]: BescheidSendBy.NACHRICHT, + [BescheidFormService.FIELD_SEND_BY]: sendBy, [BescheidFormService.FIELD_BESCHEID_DOCUMENT]: bescheidDocumentUri, [BescheidFormService.FIELD_NACHRICHT_SUBJECT]: bescheidResource.nachrichtSubject, [BescheidFormService.FIELD_NACHRICHT_TEXT]: bescheidResource.nachrichtText, @@ -479,6 +479,44 @@ describe('BescheidFormService', () => { }); }); + describe('_evaluateSendBy', () => { + it('should return MANUAL on MANUAL', () => { + const sendBy: BescheidSendBy = service._evaluateSendBy({ + ...createBescheidResource([BescheidLinkRel.BESCHEIDEN]), + sendBy: BescheidSendBy.MANUAL, + }); + + expect(sendBy).toEqual(BescheidSendBy.MANUAL); + }); + + it('should return MANUAL on NACHRICHT and missing link', () => { + const sendBy: BescheidSendBy = service._evaluateSendBy({ + ...createBescheidResource([BescheidLinkRel.BESCHEIDEN]), + sendBy: BescheidSendBy.NACHRICHT, + }); + + expect(sendBy).toEqual(BescheidSendBy.MANUAL); + }); + + it('should return NACHRICHT', () => { + const sendBy: BescheidSendBy = service._evaluateSendBy({ + ...createBescheidResource([BescheidLinkRel.BESCHEIDEN_UND_SENDEN]), + sendBy: BescheidSendBy.NACHRICHT, + }); + + expect(sendBy).toEqual(BescheidSendBy.NACHRICHT); + }); + + it('should return MANUAL', () => { + const sendBy: BescheidSendBy = service._evaluateSendBy({ + ...createBescheidResource([BescheidLinkRel.BESCHEIDEN_UND_SENDEN]), + sendBy: BescheidSendBy.MANUAL, + }); + + expect(sendBy).toEqual(BescheidSendBy.MANUAL); + }); + }); + describe('getBescheidFormValueChanges', () => { const bescheid: Bescheid = createBescheid(); diff --git a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid.formservice.ts b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid.formservice.ts index 4e650ff9f6660e2e987ee53979858474096cafe2..eb96bb9d90af5f6729a3f251ea01bc2e91c58eb7 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid.formservice.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid-wizard-container/bescheid.formservice.ts @@ -21,21 +21,14 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - Bescheid, - BescheidLinkRel, - BescheidResource, - BescheidSendBy, - BescheidWizardStep, - Wizard, -} from '@alfa-client/bescheid-shared'; +import { Bescheid, BescheidLinkRel, BescheidResource, BescheidSendBy, BescheidWizardStep, Wizard, } from '@alfa-client/bescheid-shared'; import { CommandResource, switchMapCommandSuccessfullyDone } from '@alfa-client/command-shared'; import { AbstractFormService, convertToBoolean, formatForDatabase, isNotNil, StateResource } from '@alfa-client/tech-shared'; import { VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; import { Injectable } from '@angular/core'; import { FormControl, UntypedFormArray, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { getUrl, hasLink, ResourceUri } from '@ngxp/rest'; -import { isNil, isUndefined } from 'lodash-es'; +import { isNil } from 'lodash-es'; import { first, Observable, startWith, switchMap } from 'rxjs'; import { BescheidService2 } from '../../../../bescheid-shared/src/lib/bescheid2.service'; @@ -118,12 +111,13 @@ export class BescheidFormService extends AbstractFormService<CommandResource> { public patchValues(bescheidResource: BescheidResource): void { this.bescheidResource = bescheidResource; const bescheidDocumentUri: ResourceUri = this.getBescheidDocumentUri(bescheidResource); + const sendBy: BescheidSendBy = this._evaluateSendBy(bescheidResource); + this.bescheidService.setSendBy(sendBy); this.patch({ [BescheidFormService.FIELD_BESCHIEDEN_AM]: bescheidResource.beschiedenAm, [BescheidFormService.FIELD_BEWILLIGT]: String(bescheidResource.bewilligt), [BescheidFormService.FIELD_BESCHEID_DOCUMENT]: bescheidDocumentUri, - [BescheidFormService.FIELD_SEND_BY]: - isUndefined(bescheidResource.sendBy) ? BescheidSendBy.NACHRICHT : bescheidResource.sendBy, + [BescheidFormService.FIELD_SEND_BY]: sendBy, [BescheidFormService.FIELD_NACHRICHT_SUBJECT]: bescheidResource.nachrichtSubject, [BescheidFormService.FIELD_NACHRICHT_TEXT]: bescheidResource.nachrichtText, }); @@ -142,6 +136,16 @@ export class BescheidFormService extends AbstractFormService<CommandResource> { return null; } + _evaluateSendBy(bescheidResource: BescheidResource): BescheidSendBy { + if ( + bescheidResource.sendBy === BescheidSendBy.NACHRICHT && + hasLink(bescheidResource, BescheidLinkRel.BESCHEIDEN_UND_SENDEN) + ) { + return BescheidSendBy.NACHRICHT; + } + return BescheidSendBy.MANUAL; + } + public getBescheidFormValueChanges(): Observable<Bescheid> { return this.form.valueChanges.pipe(startWith(this.getBescheidFormValue())); } diff --git a/alfa-client/libs/bescheid/src/lib/bescheid.module.ts b/alfa-client/libs/bescheid/src/lib/bescheid.module.ts index b8f0eb5dafcd5b84ec2d13457ae195d2f4589ef9..f098c06d4ec7b163b55a8bad5f60c7682521866c 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid.module.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid.module.ts @@ -22,24 +22,12 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BescheidSharedModule } from '@alfa-client/bescheid-shared'; -import { BinaryFileModule } from '@alfa-client/binary-file'; +import { BinaryFileModule, FileUploadListContainerComponent, MultiFileUploadEditorComponent } from '@alfa-client/binary-file'; import { CommandSharedModule } from '@alfa-client/command-shared'; -import { - ConvertForDataTestPipe, - ConvertProblemDetailToErrorMessagesPipe, - GetUrlPipe, - HasLinkPipe, - ToEmbeddedResourcesPipe, -} from '@alfa-client/tech-shared'; +import { ConvertForDataTestPipe, ConvertProblemDetailToErrorMessagesPipe, GetUrlPipe, HasLinkPipe, ToEmbeddedResourcesPipe, } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { - ButtonWithSpinnerComponent, - FileUploadEditorComponent, - SingleFileUploadEditorComponent, - TextareaEditorComponent, - TextEditorComponent, -} from '@ods/component'; +import { ButtonWithSpinnerComponent, FileUploadEditorComponent, SingleFileUploadEditorComponent, TextareaEditorComponent, TextEditorComponent, } from '@ods/component'; import { BescheidInVorgangContainerComponent } from './bescheid-in-vorgang-container/bescheid-in-vorgang-container.component'; import { BescheidInVorgangComponent } from './bescheid-in-vorgang-container/bescheid-in-vorgang/bescheid-in-vorgang.component'; import { BescheidListInVorgangContainerComponent } from './bescheid-list-in-vorgang-container/bescheid-list-in-vorgang-container.component'; @@ -48,30 +36,10 @@ import { DocumentInBescheidContainerComponent } from './bescheid-list-in-vorgang import { BeschiedenDateContainerComponent } from './beschieden-date-in-vorgang-container/beschieden-date-container/beschieden-date-container.component'; import { BeschiedenDateInVorgangContainerComponent } from './beschieden-date-in-vorgang-container/beschieden-date-in-vorgang-container.component'; -import { - DateEditorComponent, - ExpansionPanelComponent, - OzgcloudStrokedButtonWithSpinnerComponent, - SpinnerComponent, -} from '@alfa-client/ui'; +import { DateEditorComponent, ExpansionPanelComponent, OzgcloudStrokedButtonWithSpinnerComponent, SpinnerComponent, } from '@alfa-client/ui'; import { ReactiveFormsModule } from '@angular/forms'; import { MatIcon } from '@angular/material/icon'; -import { - AttachmentComponent, - AttachmentWrapperComponent, - BescheidGenerateIconComponent, - BescheidStatusTextComponent, - BescheidUploadIconComponent, - BescheidWrapperComponent, - ButtonCardComponent, - ButtonComponent, - CloseIconComponent, - RadioButtonCardComponent, - SaveIconComponent, - SendIconComponent, - SpinnerIconComponent, - StampIconComponent, -} from '@ods/system'; +import { AttachmentComponent, AttachmentWrapperComponent, BescheidGenerateIconComponent, BescheidStatusTextComponent, BescheidUploadIconComponent, BescheidWrapperComponent, ButtonCardComponent, ButtonComponent, CheckIconComponent, CloseIconComponent, RadioButtonCardComponent, SaveIconComponent, SendIconComponent, SpinnerIconComponent, StampIconComponent, } from '@ods/system'; import { FormatFullDatePipe } from '../../../tech-shared/src/lib/pipe/format-full-date.pipe'; import { BescheidWizardContainerComponent } from './bescheid-wizard-container/bescheid-wizard-container.component'; import { BescheidWizardAbschliessenButtonComponent } from './bescheid-wizard-container/bescheid-wizard/antrag-bescheiden/abschliessen-button/bescheid-wizard-abschliessen-button.component'; @@ -79,8 +47,6 @@ import { BescheidWizardAbschliessenDialogContainerComponent } from './bescheid-w import { BescheidWizardAntragBescheidenContainerComponent } from './bescheid-wizard-container/bescheid-wizard/antrag-bescheiden/bescheid-wizard-antrag-bescheiden-container.component'; import { BescheidWizardAntragBescheidenFormComponent } from './bescheid-wizard-container/bescheid-wizard/antrag-bescheiden/form/bescheid-wizard-antrag-bescheiden-form.component'; import { BescheidWizardAntragBescheidenSummaryComponent } from './bescheid-wizard-container/bescheid-wizard/antrag-bescheiden/summary/bescheid-wizard-antrag-bescheiden-summary.component'; -import { BescheidWizardAttachmentFilesComponent } from './bescheid-wizard-container/bescheid-wizard/attachment-files-container/attachment-files/bescheid-wizard-attachment-files.component'; -import { BescheidWizardAttachmentFilesContainerComponent } from './bescheid-wizard-container/bescheid-wizard/attachment-files-container/bescheid-wizard-attachment-files-container.component'; import { BescheidWizardBescheidVersendenContainerComponent } from './bescheid-wizard-container/bescheid-wizard/bescheid-versenden/bescheid-wizard-bescheid-versenden-container.component'; import { BescheidWizardBescheidVersendenFormComponent } from './bescheid-wizard-container/bescheid-wizard/bescheid-versenden/form/bescheid-wizard-bescheid-versenden-form.component'; import { BescheidWizardBescheidVersendenSummaryComponent } from './bescheid-wizard-container/bescheid-wizard/bescheid-versenden/summary/bescheid-wizard-bescheid-versenden-summary.component'; @@ -101,7 +67,6 @@ import { BescheidWizardStepperComponent } from './bescheid-wizard-container/besc import { BescheidWizardStepComponent } from './bescheid-wizard-container/bescheid-wizard/stepper/step/bescheid-wizard-step.component'; import { BescheidWizardSummaryComponent } from './bescheid-wizard-container/bescheid-wizard/summary/bescheid-wizard-summary.component'; import { BescheidWizardUploadAttachmentButtonContainerComponent } from './bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/bescheid-wizard-upload-attachment-button-container.component'; -import { BescheidWizardUploadAttachmentButtonComponent } from './bescheid-wizard-container/bescheid-wizard/upload-attachment-button-container/upload-attachment-button/bescheid-wizard-upload-attachment-button.component'; import { BescheidWizardUploadDocumentButtonContainerComponent } from './bescheid-wizard-container/bescheid-wizard/upload-document-button-container/bescheid-wizard-upload-document-button-container.component'; import { BescheidWizardUploadDocumentButtonComponent } from './bescheid-wizard-container/bescheid-wizard/upload-document-button-container/upload-document-button/bescheid-wizard-upload-document-button.component'; import { BescheidWizardWeiterButtonComponent } from './bescheid-wizard-container/bescheid-wizard/weiter-button/bescheid-wizard-weiter-button.component'; @@ -140,10 +105,13 @@ import { BescheidWizardWeiterButtonComponent } from './bescheid-wizard-container FileUploadEditorComponent, SendIconComponent, SaveIconComponent, + CheckIconComponent, TextEditorComponent, TextareaEditorComponent, ConvertForDataTestPipe, ConvertProblemDetailToErrorMessagesPipe, + FileUploadListContainerComponent, + MultiFileUploadEditorComponent, ], declarations: [ BescheidInVorgangContainerComponent, @@ -177,9 +145,6 @@ import { BescheidWizardWeiterButtonComponent } from './bescheid-wizard-container BescheidWizardDocumentFileContainerComponent, BescheidWizardDocumentFileComponent, BescheidWizardUploadAttachmentButtonContainerComponent, - BescheidWizardUploadAttachmentButtonComponent, - BescheidWizardAttachmentFilesContainerComponent, - BescheidWizardAttachmentFilesComponent, BescheidWizardBescheidVersendenContainerComponent, BescheidWizardBescheidVersendenFormComponent, BescheidWizardBescheidVersendenSummaryComponent, diff --git a/alfa-client/libs/binary-file-shared/src/index.ts b/alfa-client/libs/binary-file-shared/src/index.ts index 84a2d2d428e583925dbf769d0d3e759aec1d4469..89abbed2825506534bc5be9bb248855c2c2c10dc 100644 --- a/alfa-client/libs/binary-file-shared/src/index.ts +++ b/alfa-client/libs/binary-file-shared/src/index.ts @@ -27,4 +27,3 @@ export * from './lib/binary-file-shared.module'; export * from './lib/binary-file.linkrel'; export * from './lib/binary-file.model'; export * from './lib/binary-file.service'; -export * from './lib/binary-file.util'; diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.model.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.model.ts index b315da1908251ee8a163da0ac755d1f9d0fb20f7..68b3862e4a27ccfa63c8c66930f1d980af233663 100644 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.model.ts +++ b/alfa-client/libs/binary-file-shared/src/lib/binary-file.model.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ListResource } from '@alfa-client/tech-shared'; +import { ListResource, StateResource } from '@alfa-client/tech-shared'; import { Resource } from '@ngxp/rest'; export interface BinaryFile { @@ -33,3 +33,38 @@ export interface BinaryFile { export interface BinaryFileResource extends BinaryFile, Resource {} export interface BinaryFileListResource extends ListResource {} + +export interface ToUploadFile { + type: FileUploadType; + file: File; + uploadUrl: string; +} + +export declare type FileUploadType = string; + +export interface UploadFile { + fileToUpload?: File; + uploadedFile: StateResource<BinaryFileResource>; +} + +export type UploadFileByIdentifier = { [key: string]: UploadFile }; +export type UploadFilesByType = { [type: string]: UploadFileByIdentifier }; + +export interface FileToDelete { + key: string; + binaryFileResource: BinaryFileResource; +} + +export enum BinaryFileIcon { + 'application/pdf' = 'pdf', + 'application/json' = 'json', + 'application/msword' = 'doc', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' = 'doc', + 'application/xml' = 'xml', + 'text/xml' = 'xml', + 'image/apng' = 'image', + 'image/gif' = 'image', + 'image/jpeg' = 'image', + 'image/png' = 'image', + 'image/svg+xml' = 'image', +} diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.repository.spec.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.repository.spec.ts index 27377b3854e4b1e87900b83f4d957a7751641b1b..dfec001d129c806b4212ebb4dce6f0e54d40213e 100644 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.repository.spec.ts +++ b/alfa-client/libs/binary-file-shared/src/lib/binary-file.repository.spec.ts @@ -30,15 +30,17 @@ import { ListResource, } from '@alfa-client/tech-shared'; import { mock, mockClass, useFromMock } from '@alfa-client/test-utils'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http'; import { faker } from '@faker-js/faker'; import { Resource, ResourceFactory, ResourceUri, getUrl } from '@ngxp/rest'; import { cold, hot } from 'jest-marbles'; import { InjectorService } from 'libs/tech-shared/src/lib/injector/injector.service'; import { DummyLinkRel } from 'libs/tech-shared/test/dummy'; +import { createHttpResponse } from 'libs/tech-shared/test/http'; +import { singleCold } from 'libs/tech-shared/test/marbles'; import { createDummyListResource, createDummyResource } from 'libs/tech-shared/test/resource'; import { Observable, of } from 'rxjs'; -import { createBinaryFileResource, createBlob, createGetRequestOptions } from '../../test/binary-file'; +import { createBinaryFileResource, createBlob, createFile, createGetRequestOptions } from '../../test/binary-file'; import { BinaryFileLinkRel } from './binary-file.linkrel'; import { BinaryFileResource } from './binary-file.model'; import { BinaryFileRepository } from './binary-file.repository'; @@ -69,10 +71,52 @@ describe('BinaryFileRepository', () => { expect(repository).toBeTruthy(); }); + describe('upload file new', () => { + const dummyLinkRel: string = DummyLinkRel.DUMMY; + const dummyResource: Resource = createDummyResource([dummyLinkRel]); + const uri: ResourceUri = getUrl(dummyResource, dummyLinkRel); + + const file: File = createFile(); + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + const httpResponse: HttpResponse<Object> = createHttpResponse(); + + const blob: Blob = createBlob(); + + beforeEach(() => { + httpClient.post.mockReturnValue(of(httpResponse)); + repository.getFile = jest.fn().mockReturnValue(of(binaryFileResource)); + }); + + it('should call post on http client', () => { + const formData: FormData = new FormData(); + formData.append('file', blob, file.name); + + repository.uploadFileNew(uri, <File>blob); + + expect(httpClient.post).toHaveBeenCalledWith(getUrl(dummyResource, dummyLinkRel), formData, { + observe: 'response', + }); + }); + + it('should call get file', () => { + repository.uploadFileNew(uri, <File>blob).subscribe(); + + expect(repository.getFile).toHaveBeenCalledWith(httpResponse.headers.get(HttpHeader.LOCATION)); + }); + + it('should return binary file', () => { + repository.getFile = jest.fn().mockReturnValue(singleCold(binaryFileResource)); + + const result: Observable<BinaryFileResource> = repository.uploadFileNew(uri, <File>blob); + + expect(result).toBeObservable(singleCold(binaryFileResource)); + }); + }); + describe('uploadFile', () => { const dummyLinkRel: string = DummyLinkRel.DUMMY; const dummyResource: Resource = createDummyResource([dummyLinkRel]); - const blob: Blob = new Blob(['test text'], { type: 'text/plain' }); + const blob: Blob = createBlob(); beforeEach(() => { httpClient.post.mockReturnValue(of({})); @@ -82,7 +126,7 @@ describe('BinaryFileRepository', () => { const formData: FormData = new FormData(); formData.append('file', blob, 'filename'); - repository.uploadFile(dummyResource, dummyLinkRel, <File>blob); + repository.uploadFile(getUrl(dummyResource, dummyLinkRel), <File>blob); expect(httpClient.post).toHaveBeenCalledWith(getUrl(dummyResource, dummyLinkRel), formData, { observe: 'response', @@ -90,7 +134,7 @@ describe('BinaryFileRepository', () => { }); it('and return result', () => { - let result = repository.uploadFile(dummyResource, dummyLinkRel, <File>blob); + const result: Observable<HttpResponse<Object>> = repository.uploadFile(getUrl(dummyResource, dummyLinkRel), <File>blob); expect(result).not.toBeNull(); }); diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.repository.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.repository.ts index c98079edeeaaf591b8efdb327dd5a5e5a5fe16c1..c3cdeb7e53cb71d9771066f8ad7e61138636b435 100644 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.repository.ts +++ b/alfa-client/libs/binary-file-shared/src/lib/binary-file.repository.ts @@ -32,7 +32,7 @@ import { import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Resource, ResourceFactory, ResourceUri, getUrl } from '@ngxp/rest'; -import { Observable, map } from 'rxjs'; +import { Observable, map, mergeMap } from 'rxjs'; import { BinaryFileLinkRel } from './binary-file.linkrel'; import { BinaryFileListResource, BinaryFileResource } from './binary-file.model'; @@ -43,23 +43,33 @@ export class BinaryFileRepository { private resourceFactory: ResourceFactory, ) {} - public uploadFile( - resource: Resource, - linkRel: string, - file: File, - ): Observable<HttpResponse<Object>> { + public uploadFileNew(uri: ResourceUri, file: File): Observable<BinaryFileResource> { const formData: FormData = new FormData(); formData.append('file', file, file.name); - return this.httpClient.post(getUrl(resource, linkRel), formData, { observe: 'response' }); + return this.httpClient + .post(uri, formData, { observe: 'response' }) + .pipe(mergeMap((response: HttpResponse<Object>) => this.getFile(this.getLocation(response)))); + } + + private getLocation(response: HttpResponse<Object>): ResourceUri { + return response.headers.get(HttpHeader.LOCATION); + } + + /** + * @deprecated Use uploadFileNew instead + */ + public uploadFile(uri: ResourceUri, file: File): Observable<HttpResponse<Object>> { + const formData: FormData = new FormData(); + + formData.append('file', file, file.name); + + return this.httpClient.post(uri, formData, { observe: 'response' }); } public download(fileResource: BinaryFileResource): Observable<Blob> { - return this.doDownload( - getUrl(fileResource, BinaryFileLinkRel.DOWNLOAD), - this.buildRequestOptions(), - ); + return this.doDownload(getUrl(fileResource, BinaryFileLinkRel.DOWNLOAD), this.buildRequestOptions()); } buildRequestOptions(): GetRequestOptions { @@ -76,10 +86,7 @@ export class BinaryFileRepository { } buildPdfRequestOptions(): GetRequestOptions { - return this.buildBaseRequestOptions([ - ContentType.APPLICATION_PDF, - ContentType.APPLICATION_JSON, - ]); + return this.buildBaseRequestOptions([ContentType.APPLICATION_PDF, ContentType.APPLICATION_JSON]); } buildBaseRequestOptions(contentTypes: ContentType[]): GetRequestOptions { @@ -97,9 +104,7 @@ export class BinaryFileRepository { } public downloadArchive(uri: ResourceUri): Observable<BlobWithFileName> { - return this.httpClient - .get<HttpResponse<Blob>>(uri, this.buildRequestOptionsForArchive()) - .pipe(map(buildBlobWithFileName)); + return this.httpClient.get<HttpResponse<Blob>>(uri, this.buildRequestOptionsForArchive()).pipe(map(buildBlobWithFileName)); } buildRequestOptionsForArchive(): GetRequestOptions { diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.spec.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.spec.ts index 0f2d961a4b48fae71acf2ed14685e34697da1b22..f32b65dd32e10f8754cb8e138122312211bdac26 100644 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.spec.ts +++ b/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.spec.ts @@ -21,22 +21,25 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { BlobWithFileName, StateResource, createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared'; +import { BlobWithFileName, createEmptyStateResource, createErrorStateResource, createStateResource, StateResource, } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { SnackBarService } from '@alfa-client/ui'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { fakeAsync, tick } from '@angular/core/testing'; import { faker } from '@faker-js/faker'; -import { Resource, ResourceUri } from '@ngxp/rest'; +import { expect } from '@jest/globals'; +import { getUrl, Resource, ResourceUri } from '@ngxp/rest'; import { cold, hot } from 'jest-marbles'; -import { createBinaryFileResource, createBlob } from 'libs/binary-file-shared/test/binary-file'; +import { createBinaryFileResource, createBlob, createFile, createUploadFile } from 'libs/binary-file-shared/test/binary-file'; import { VALIDATION_MESSAGES, ValidationMessageCode } from 'libs/tech-shared/src/lib/validation/tech.validation.messages'; import { DummyLinkRel } from 'libs/tech-shared/test/dummy'; import { createDummyResource } from 'libs/tech-shared/test/resource'; +import { uniqueId } from 'lodash-es'; import { Observable, of, throwError } from 'rxjs'; +import { createApiError } from '../../../tech-shared/test/error'; import { createHttpErrorResponse } from '../../../tech-shared/test/http'; -import { singleHot } from '../../../tech-shared/test/marbles'; -import { BinaryFileResource } from './binary-file.model'; +import { multipleCold, singleCold, singleHot } from '../../../tech-shared/test/marbles'; +import { BinaryFileResource, FileUploadType, ToUploadFile, UploadFile, UploadFileByIdentifier } from './binary-file.model'; import { BinaryFileRepository } from './binary-file.repository'; import { BinaryFileService } from './binary-file.service'; @@ -57,6 +60,323 @@ describe('BinaryFileService', () => { expect(service).toBeTruthy(); }); + describe('addFiles', () => { + const type: FileUploadType = faker.word.noun(); + const uniqueId: string = faker.word.noun(); + + beforeEach(() => { + service._generateUniqueId = jest.fn().mockReturnValue(uniqueId); + }); + + it('should not set uploaded file', () => { + service.addFiles(type, null); + + expect(service._uploadFiles$).toBeObservable(singleCold({})); + }); + + it('should generate unique id', () => { + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + + service.addFiles(type, [binaryFileResource]); + + expect(service._generateUniqueId).toHaveBeenCalled(); + }); + + it('should set uploaded file', () => { + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + + service.addFiles(type, [binaryFileResource]); + + const uploadedFile: UploadFile = service._uploadFiles$.value[type][uniqueId]; + expect(uploadedFile).toEqual({ uploadedFile: createStateResource(binaryFileResource) } as UploadFile); + }); + }); + + describe('upload file new', () => { + const uniqId: string = uniqueId(); + const type: string = 'dummyType'; + const file: File = createFile(); + const toUploadFile: ToUploadFile = { type, file, uploadUrl: faker.internet.url() }; + + beforeEach(() => { + service._clearFailedUploads = jest.fn(); + service._generateUniqueId = jest.fn().mockReturnValue(uniqId); + service._addUploadFileLoading = jest.fn(); + service._doUploadFile = jest.fn(); + }); + + it('should clear failed uploads', () => { + service.uploadFileNew(toUploadFile); + + expect(service._clearFailedUploads).toHaveBeenCalled(); + }); + + it('should create an empty map if type key not exists', () => { + service._uploadFiles$.next({}); + + service.uploadFileNew(toUploadFile); + + expect(service._uploadFiles$.value[type]).not.toBeUndefined(); + }); + + it('should call addUploadFileLoading', () => { + service.uploadFileNew(toUploadFile); + + expect(service._addUploadFileLoading).toHaveBeenCalledWith(uniqId, toUploadFile); + }); + + it('should call doUploadFile', () => { + service.uploadFileNew(toUploadFile); + + expect(service._doUploadFile).toHaveBeenCalledWith(uniqId, toUploadFile); + }); + }); + + describe('clear failed uploads', () => { + const fileUploadType: string = faker.word.noun(); + const failedFileKey: string = faker.word.noun(); + const successFileKey: string = faker.word.noun(); + const successUploadFile: UploadFile = createUploadFile(); + const secondSuccessFileKey: string = faker.word.noun(); + const secondFileUploadType: string = faker.word.noun(); + const secondSuccessUploadFile: UploadFile = createUploadFile(); + + it('should remove failed uploads', () => { + service._uploadFiles$.next({ + [fileUploadType]: { + [failedFileKey]: { ...createUploadFile(), uploadedFile: createErrorStateResource(createApiError()) }, + [successFileKey]: successUploadFile, + }, + [secondFileUploadType]: { + [secondSuccessFileKey]: secondSuccessUploadFile, + }, + }); + + service._clearFailedUploads(fileUploadType); + + expect(service._uploadFiles$.value).toEqual({ + [fileUploadType]: { + [successFileKey]: successUploadFile, + }, + [secondFileUploadType]: { + [secondSuccessFileKey]: secondSuccessUploadFile, + }, + }); + }); + }); + + describe('add upload file loading', () => { + const uniqId: string = uniqueId(); + const type: string = 'dummyType'; + const file: File = createFile(); + const toUploadFile: ToUploadFile = { type, file, uploadUrl: faker.internet.url() }; + + it('should add loading entry by type', () => { + service._addUploadFileLoading(uniqId, toUploadFile); + + expect((<UploadFileByIdentifier>service._uploadFiles$.value[type])[uniqId]).toEqual({ + fileToUpload: file, + uploadedFile: createEmptyStateResource(true), + }); + }); + + it('should add loading entry by type and keep existing entries', () => { + service._uploadFiles$.next({ + ...service._uploadFiles$.value, + [toUploadFile.type]: { + ['keepId']: { fileToUpload: toUploadFile.file, uploadedFile: createStateResource(createBinaryFileResource()) }, + }, + }); + + service._addUploadFileLoading(uniqId, toUploadFile); + + expect((<UploadFileByIdentifier>service._uploadFiles$.value[type])['keepId']).not.toBeUndefined(); + expect((<UploadFileByIdentifier>service._uploadFiles$.value[type])[uniqId]).not.toBeUndefined(); + }); + }); + + describe('do upload file', () => { + const uniqId: string = 'id'; + const type: string = 'dummyType'; + const file: File = createFile(); + const toUploadFile: ToUploadFile = { type, file, uploadUrl: faker.internet.url() }; + + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + + beforeEach(() => { + repository.uploadFileNew.mockReturnValue(of(binaryFileResource)); + service._handleError = jest.fn(); + service._updateUploadedFile = jest.fn(); + }); + + it('should call repository', () => { + service._doUploadFile(uniqId, toUploadFile); + + expect(repository.uploadFileNew).toHaveBeenCalledWith(toUploadFile.uploadUrl, file); + }); + + it('should call update uploaded file', () => { + service._doUploadFile(uniqId, toUploadFile); + + expect(service._updateUploadedFile).toHaveBeenCalledWith(uniqId, toUploadFile, createStateResource(binaryFileResource)); + }); + + it('should handle error', () => { + const errorResponse: HttpErrorResponse = createHttpErrorResponse(); + repository.uploadFileNew.mockReturnValue(throwError(() => errorResponse)); + + service._doUploadFile(uniqId, toUploadFile); + + expect(service._handleError).toHaveBeenCalledWith(errorResponse.error, false); + }); + + it('should update uploaded file on error', () => { + const errorResponse: HttpErrorResponse = createHttpErrorResponse(); + service._handleError = jest.fn().mockReturnValue(of(createStateResource(errorResponse.error))); + repository.uploadFileNew.mockReturnValue(throwError(() => errorResponse)); + + service._doUploadFile(uniqId, toUploadFile); + + expect(service._updateUploadedFile).toHaveBeenCalledWith(uniqId, toUploadFile, createStateResource(errorResponse.error)); + }); + }); + + describe('update upload file state', () => { + const type: string = 'dummyType'; + const file: File = createFile(); + const toUploadFile: ToUploadFile = { type, file, uploadUrl: faker.internet.url() }; + + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + + it('should set resource to state value', () => { + service._uploadFiles$.next({ + ...service._uploadFiles$.value, + [toUploadFile.type]: { + ['keepEntryId']: { fileToUpload: toUploadFile.file, uploadedFile: createStateResource(binaryFileResource) }, + ['updateEntryId']: { fileToUpload: toUploadFile.file, uploadedFile: createEmptyStateResource(true) }, + }, + }); + + service._updateUploadedFile('updateEntryId', toUploadFile, createStateResource(binaryFileResource)); + + expect((<UploadFileByIdentifier>service._uploadFiles$.value[type])['keepEntryId']).not.toBeUndefined(); + expect((<UploadFileByIdentifier>service._uploadFiles$.value[type])['updateEntryId']).not.toBeUndefined(); + }); + }); + + describe('get uploaded files', () => { + const type: FileUploadType = 'DummyType'; + const uploadFile: UploadFile = createUploadFile(); + + it('should return uploaded files by key', (done) => { + service._uploadFiles$.next({ + ...service._uploadFiles$.value, + [type]: { + ['keepEntryId']: uploadFile, + }, + }); + + service.getUploadedFiles(type).subscribe((uploadedFiles: UploadFileByIdentifier) => { + expect(uploadedFiles).toEqual({ ['keepEntryId']: uploadFile }); + done(); + }); + }); + + describe('on non existing key', () => { + beforeEach(() => { + service._uploadFiles$.next({}); + }); + + it('should return empty object', (done) => { + service.getUploadedFiles(type).subscribe((uploadedFiles: UploadFileByIdentifier) => { + expect(uploadedFiles).toEqual({}); + done(); + }); + }); + + it('should set state value', () => { + service.getUploadedFiles(type).subscribe(); + + expect(service._uploadFiles$.value[type]).toEqual({}); + }); + }); + }); + + describe('is upload in progress', () => { + const type: FileUploadType = 'DummyType'; + const loadingUploadedFileEntry = { + fileToUpload: createFile(), + uploadedFile: createEmptyStateResource<BinaryFileResource>(true), + }; + const loadedUploadedFileEntry = { + fileToUpload: createFile(), + uploadedFile: createStateResource<BinaryFileResource>(createBinaryFileResource()), + }; + + it('should return false on empty state', () => { + const uploadInProgress: Observable<boolean> = service.isUploadInProgress(type); + + expect(uploadInProgress).toBeObservable(singleCold(false)); + }); + + it('should return true if uploadedFiles contains loading stateResource by key', () => { + service._uploadFiles$.next({ + ...service._uploadFiles$.value, + [type]: { ['loadingEntryId']: loadingUploadedFileEntry, ['loadedEntryId']: loadedUploadedFileEntry }, + }); + + const uploadInProgress: Observable<boolean> = service.isUploadInProgress(type); + + expect(uploadInProgress).toBeObservable(singleCold(true)); + }); + + it('should return false if uploadedFiles contains loaded stateResources only', () => { + service._uploadFiles$.next({ + ...service._uploadFiles$.value, + [type]: { ['loadedEntry1Id']: loadedUploadedFileEntry, ['loadedEntry2Id']: loadedUploadedFileEntry }, + }); + + const uploadInProgress: Observable<boolean> = service.isUploadInProgress(type); + + expect(uploadInProgress).toBeObservable(singleCold(false)); + }); + }); + + describe('delete uploaded file', () => { + const type: string = 'dummyType'; + const file: File = createFile(); + const toUploadFile: ToUploadFile = { type, file, uploadUrl: faker.internet.url() }; + + it('should remove entry by type and key', () => { + service._uploadFiles$.next({ + ...service._uploadFiles$.value, + [toUploadFile.type]: { + ['keepEntryId']: { fileToUpload: toUploadFile.file, uploadedFile: createStateResource(createBinaryFileResource()) }, + ['toRemoveEntryId']: { + fileToUpload: toUploadFile.file, + uploadedFile: createStateResource(createBinaryFileResource()), + }, + }, + }); + + service.deleteUploadedFile(toUploadFile.type, 'toRemoveEntryId'); + + expect((<UploadFileByIdentifier>service._uploadFiles$.value[type])['keepEntryId']).not.toBeUndefined(); + expect((<UploadFileByIdentifier>service._uploadFiles$.value[type])['toRemoveEntryId']).toBeUndefined(); + }); + }); + + describe('clear uploaded files', () => { + it('should remove map by type from state', () => { + const type: FileUploadType = 'dummyType'; + service._uploadFiles$.next({ ...service._uploadFiles$.value, [type]: {} }); + + service.clearUploadedFiles(type); + + expect(service._uploadFiles$.value.hasOwnProperty(type)).toBeFalsy(); + }); + }); + describe('download file', () => { const binaryFileResource: BinaryFileResource = createBinaryFileResource(); const downloadNamePrefix: string = 'VorgangsNummerAsPrefix'; @@ -79,12 +399,7 @@ describe('BinaryFileService', () => { const returnValue: Observable<StateResource<Blob>> = service.downloadFile(binaryFileResource, downloadNamePrefix); - expect(returnValue).toBeObservable( - cold('ab', { - a: createEmptyStateResource(true), - b: createStateResource(blob), - }), - ); + expect(returnValue).toBeObservable(multipleCold(createEmptyStateResource(true), createStateResource(blob))); }); describe('save data', () => { @@ -203,8 +518,8 @@ describe('BinaryFileService', () => { }); describe('upload file', () => { - const dummyResource: Resource = createDummyResource(); const dummyLinkRel: string = DummyLinkRel.DUMMY; + const dummyResource: Resource = createDummyResource([dummyLinkRel]); const fileLocation: string = 'fileLocation'; const uploadFileResponse: HttpResponse<Object> = <any>{ @@ -222,7 +537,7 @@ describe('BinaryFileService', () => { it('should call repository', () => { service.uploadFile(dummyResource, dummyLinkRel, testFile); - expect(repository.uploadFile).toHaveBeenCalledWith(dummyResource, dummyLinkRel, testFile); + expect(repository.uploadFile).toHaveBeenCalledWith(getUrl(dummyResource, dummyLinkRel), testFile); }); it.skip('should call get file', fakeAsync(() => { diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.ts index e06c03dff2f58d8e33467a52c08fc6a5f5e2f047..b6a628e32f0ef01e8ab34a02fc44a486ce1e69e9 100644 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.ts +++ b/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.ts @@ -21,52 +21,144 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - BlobWithFileName, - EMPTY_STRING, - HttpHeader, - StateResource, - createEmptyStateResource, - createErrorStateResource, - createStateResource, - getMessageForInvalidParam, - isNotNil, - isUnprocessableEntity, - isValidationFieldFileSizeExceedError, - sanitizeFileName, -} from '@alfa-client/tech-shared'; +import { BlobWithFileName, createEmptyStateResource, createErrorStateResource, createStateResource, EMPTY_STRING, getMessageForInvalidParam, hasStateResourceError, HttpHeader, isNotNil, isUnprocessableEntity, isValidationFieldFileSizeExceedError, sanitizeFileName, StateResource, } from '@alfa-client/tech-shared'; import { SnackBarService } from '@alfa-client/ui'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Resource, ResourceUri } from '@ngxp/rest'; +import { getUrl, Resource, ResourceUri } from '@ngxp/rest'; import { saveAs } from 'file-saver'; -import { isNil } from 'lodash-es'; -import { Observable, of, throwError } from 'rxjs'; -import { catchError, map, mergeMap, startWith } from 'rxjs/operators'; -import { BinaryFileListResource, BinaryFileResource } from './binary-file.model'; +import { isNil, uniqueId } from 'lodash-es'; +import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; +import { catchError, first, map, mergeMap, startWith } from 'rxjs/operators'; +import { BinaryFileListResource, BinaryFileResource, FileUploadType, ToUploadFile, UploadFile, UploadFileByIdentifier, UploadFilesByType, } from './binary-file.model'; import { BinaryFileRepository } from './binary-file.repository'; @Injectable({ providedIn: 'root' }) export class BinaryFileService { + _uploadFiles$: BehaviorSubject<UploadFilesByType> = new BehaviorSubject({}); + constructor( private repository: BinaryFileRepository, private snackbarService: SnackBarService, ) {} + public addFiles(type: FileUploadType, binaryFileResources: BinaryFileResource[]): void { + if (isNil(binaryFileResources)) return; + binaryFileResources.forEach((resource: BinaryFileResource) => + this.setUploadedFile(this._generateUniqueId(), type, { uploadedFile: createStateResource(resource) }), + ); + } + + public uploadFileNew(toUploadFile: ToUploadFile): void { + this._clearFailedUploads(toUploadFile.type); + this.createEmptyMapIfTypeNotExists(toUploadFile.type); + const uniqId: string = this._generateUniqueId(); + this._addUploadFileLoading(uniqId, toUploadFile); + this._doUploadFile(uniqId, toUploadFile); + } + + _clearFailedUploads(fileUploadType: FileUploadType): void { + const uploads: UploadFileByIdentifier = this._uploadFiles$.value[fileUploadType]; + const keys: string[] = Object.keys(uploads); + const successfulUploads: UploadFileByIdentifier = {}; + for (const key of keys) { + if (!hasStateResourceError(uploads[key].uploadedFile)) { + successfulUploads[key] = uploads[key]; + } + } + this._uploadFiles$.next({ ...this._uploadFiles$.value, [fileUploadType]: successfulUploads }); + } + + _generateUniqueId(): string { + return uniqueId(); + } + + _addUploadFileLoading(uniqId: string, toUploadFile: ToUploadFile): void { + this.setUploadedFile(uniqId, toUploadFile.type, { + fileToUpload: toUploadFile.file, + uploadedFile: createEmptyStateResource(true), + }); + } + + _doUploadFile(uniqId: string, toUploadFile: ToUploadFile): void { + this.repository + .uploadFileNew(toUploadFile.uploadUrl, toUploadFile.file) + .pipe( + first(), + map((resource: BinaryFileResource) => createStateResource(resource)), + catchError((errorResponse) => this._handleError(errorResponse.error, false)), + ) + .subscribe((stateResource: StateResource<BinaryFileResource>) => + this._updateUploadedFile(uniqId, toUploadFile, stateResource), + ); + } + + _updateUploadedFile( + uniqId: string, + toUploadFile: ToUploadFile, + binaryFileStateResource: StateResource<BinaryFileResource>, + ): void { + this.setUploadedFile(uniqId, toUploadFile.type, { + fileToUpload: toUploadFile.file, + uploadedFile: binaryFileStateResource, + }); + } + + private setUploadedFile(uniqId: string, type: FileUploadType, uploadedFile: UploadFile): void { + this._uploadFiles$.next({ + ...this._uploadFiles$.value, + [type]: { ...this._uploadFiles$.value[type], [uniqId]: uploadedFile }, + }); + } + + public getUploadedFiles(type: FileUploadType): Observable<UploadFileByIdentifier> { + this.createEmptyMapIfTypeNotExists(type); + return this._uploadFiles$.asObservable().pipe(map((files: UploadFilesByType) => files[type])); + } + + private createEmptyMapIfTypeNotExists(type: FileUploadType): void { + if (!(type in this._uploadFiles$.value)) this._uploadFiles$.value[type] = {}; + } + + public isUploadInProgress(type: FileUploadType): Observable<boolean> { + return this._uploadFiles$.asObservable().pipe( + map((files: UploadFilesByType) => Object.values(files[type] || []).map((file: UploadFile) => file.uploadedFile)), + map((files: StateResource<BinaryFileResource>[]) => + files.some((stateResource: StateResource<BinaryFileResource>) => stateResource.loading), + ), + ); + } + + public deleteUploadedFile(type: FileUploadType, key: string): void { + const currentMap: UploadFileByIdentifier = this._uploadFiles$.value[type]; + this._uploadFiles$.next({ + ...this._uploadFiles$.value, + [type]: Object.keys(currentMap).reduce((acc, uploadFileKey) => { + if (uploadFileKey !== key) acc[uploadFileKey] = currentMap[uploadFileKey]; + return acc; + }, {}), + }); + } + + public clearUploadedFiles(type: FileUploadType): void { + delete this._uploadFiles$.value[type]; + } + + //TODO Rename to uploadFileOld OR refactor all use cases to uploadFileNew public uploadFile( resource: Resource, linkRel: string, file: File, showValidationErrorSnackBar: boolean = true, ): Observable<StateResource<BinaryFileResource>> { - return this.repository.uploadFile(resource, linkRel, file).pipe( + return this.repository.uploadFile(getUrl(resource, linkRel), file).pipe( mergeMap((response: HttpResponse<Object>) => this.getFile(response.headers.get(HttpHeader.LOCATION))), - catchError((errorResponse) => this.handleError(errorResponse.error, showValidationErrorSnackBar)), + catchError((errorResponse) => this._handleError(errorResponse.error, showValidationErrorSnackBar)), startWith(createEmptyStateResource<BinaryFileResource>(true)), ); } - private handleError(errorResponse: HttpErrorResponse, showValidationErrorSnackBar: boolean): Observable<StateResource<any>> { + _handleError(errorResponse: HttpErrorResponse, showValidationErrorSnackBar: boolean): Observable<StateResource<any>> { return of(this.handleErrorByStatus(errorResponse, showValidationErrorSnackBar)); } diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.util.spec.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.util.spec.ts deleted file mode 100644 index c0ef7f3e2161b4bf245df02ac4c85b8fe4e445a4..0000000000000000000000000000000000000000 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.util.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { StateResource, createStateResource } from '@alfa-client/tech-shared'; -import { - createBinaryFileListResource, - createBinaryFileResource, -} from 'libs/binary-file-shared/test/binary-file'; -import { BinaryFileListResource, BinaryFileResource } from './binary-file.model'; -import { getBinaryFiles } from './binary-file.util'; - -describe('BinaryFile Util', () => { - describe('getBinaryFiles', () => { - it('should extract resource', () => { - const binaryFileResources: BinaryFileResource[] = [createBinaryFileResource()]; - const binaryFileListStateResource: StateResource<BinaryFileListResource> = - createStateResource(createBinaryFileListResource(binaryFileResources)); - - const resources: BinaryFileResource[] = getBinaryFiles(binaryFileListStateResource); - - expect(resources).toEqual(binaryFileResources); - }); - }); -}); diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.util.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.util.ts deleted file mode 100644 index 2f43cda4d54bbb7c13428cefc46a5580c6a63e7b..0000000000000000000000000000000000000000 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.util.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { StateResource, getEmbeddedResources } from '@alfa-client/tech-shared'; -import { BinaryFileListLinkRel } from './binary-file.linkrel'; -import { BinaryFileListResource, BinaryFileResource } from './binary-file.model'; - -export function getBinaryFiles( - binaryFileListResource: StateResource<BinaryFileListResource>, -): BinaryFileResource[] { - return getEmbeddedResources(binaryFileListResource, BinaryFileListLinkRel.FILE_LIST); -} - -export enum BinaryFileIcon { - 'application/pdf' = 'pdf', - 'application/json' = 'json', - 'application/msword' = 'doc', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' = 'doc', - 'application/xml' = 'xml', - 'text/xml' = 'xml', - 'image/apng' = 'image', - 'image/gif' = 'image', - 'image/jpeg' = 'image', - 'image/png' = 'image', - 'image/svg+xml' = 'image', -} diff --git a/alfa-client/libs/binary-file-shared/test/binary-file.ts b/alfa-client/libs/binary-file-shared/test/binary-file.ts index 9ff52a575cc75251741005b7c38acacb30389b72..b0aca564400e4ee49266e1c529f89ce2346f211c 100644 --- a/alfa-client/libs/binary-file-shared/test/binary-file.ts +++ b/alfa-client/libs/binary-file-shared/test/binary-file.ts @@ -21,13 +21,15 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { GetRequestOptions, StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { createStateResource, GetRequestOptions, StateResource } from '@alfa-client/tech-shared'; import { faker } from '@faker-js/faker'; import { BinaryFileListLinkRel } from 'libs/binary-file-shared/src/lib/binary-file.linkrel'; import { BinaryFile, BinaryFileListResource, BinaryFileResource, + ToUploadFile, + UploadFile, } from 'libs/binary-file-shared/src/lib/binary-file.model'; import { toResource } from 'libs/tech-shared/test/resource'; import { times } from 'lodash-es'; @@ -65,10 +67,35 @@ export function createLoadedBinaryFileResource(): StateResource<BinaryFileResour return createStateResource(createBinaryFileResource()); } +export function createGetRequestOptions(): GetRequestOptions { + return <GetRequestOptions>{}; +} + export function createBlob(): Blob { - return <Blob>{}; + const dummyData = new Uint8Array([65, 66, 67]); + return new Blob([dummyData], { type: 'text/plain' }); } -export function createGetRequestOptions(): GetRequestOptions { - return <GetRequestOptions>{}; +export function createFile(): File { + return { + ...createBlob(), + name: faker.string.alpha({ length: { min: 1, max: 50 } }), + lastModified: faker.date.past().getTime(), + webkitRelativePath: null, + }; +} + +export function createUploadFile(): UploadFile { + return { + fileToUpload: createFile(), + uploadedFile: createStateResource(createBinaryFileResource()), + }; +} + +export function createToUploadFile(): ToUploadFile { + return { + file: createFile(), + type: faker.word.noun(), + uploadUrl: faker.internet.url(), + }; } diff --git a/alfa-client/libs/binary-file/src/index.ts b/alfa-client/libs/binary-file/src/index.ts index 0de3c8e5665e8ef1080af949ee0833d3d7f6b762..4b274b2c74e7e1542d6f68309e12d91a6ab4a7e2 100644 --- a/alfa-client/libs/binary-file/src/index.ts +++ b/alfa-client/libs/binary-file/src/index.ts @@ -27,5 +27,8 @@ export * from './lib/binary-file-list-container/binary-file-list-container.compo export * from './lib/binary-file-uri-container/binary-file-uri-container.component'; export * from './lib/binary-file.module'; export * from './lib/binary-file2-container/binary-file2-container.component'; +export * from './lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive'; +export * from './lib/file-upload-list-container/file-upload-list-container.component'; export * from './lib/horizontal-binary-file-list/horizontal-binary-file-list.component'; +export * from './lib/multi-file-upload-editor/multi-file-upload-editor.component'; export * from './lib/vertical-binary-file-list/vertical-binary-file-list.component'; diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-attachment-container/binary-file-attachment-container.component.ts b/alfa-client/libs/binary-file/src/lib/binary-file-attachment-container/binary-file-attachment-container.component.ts index 895af8531079cc44bd5b1a76c691e9fd0c838be4..73d241e3fcf664c3a7dc19f1ac00d01a3079cfc3 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-attachment-container/binary-file-attachment-container.component.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file-attachment-container/binary-file-attachment-container.component.ts @@ -21,15 +21,9 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input } from '@angular/core'; import { BinaryFileResource, BinaryFileService } from '@alfa-client/binary-file-shared'; -import { - EMPTY_ARRAY, - StateResource, - createEmptyStateResource, - doOnValidStateResource, - isNotNil, -} from '@alfa-client/tech-shared'; +import { StateResource, createEmptyStateResource, doOnValidStateResource, isNotNil } from '@alfa-client/tech-shared'; +import { Component, Input } from '@angular/core'; import { Resource, getUrl } from '@ngxp/rest'; import { Observable, of } from 'rxjs'; import { tap } from 'rxjs/operators'; @@ -44,11 +38,11 @@ export class BinaryFileAttachmentContainerComponent { @Input() uploadStateResource: StateResource<Resource>; @Input() linkRelUploadAttachment: string; @Input() set existFiles(value: BinaryFileResource[]) { - this.fileList = isNotNil(value) ? value : EMPTY_ARRAY; + this.fileList = isNotNil(value) ? value : []; } uploadInProgress$: Observable<StateResource<Resource>> = of(createEmptyStateResource<Resource>()); - fileList: BinaryFileResource[] = EMPTY_ARRAY; + fileList: BinaryFileResource[] = []; constructor(private binaryFileService: BinaryFileService) {} @@ -63,18 +57,11 @@ export class BinaryFileAttachmentContainerComponent { uploadAndGetFile(file: File): Observable<StateResource<BinaryFileResource>> { return this.binaryFileService .uploadFile(this.uploadStateResource.resource, this.linkRelUploadAttachment, file) - .pipe( - tap((stateResource: StateResource<BinaryFileResource>) => - this.doAfterFileUpload(stateResource), - ), - ); + .pipe(tap((stateResource: StateResource<BinaryFileResource>) => this.doAfterFileUpload(stateResource))); } doAfterFileUpload(stateResource: StateResource<BinaryFileResource>): void { - doOnValidStateResource( - stateResource, - () => (this.fileList = [...this.fileList, stateResource.resource]), - ); + doOnValidStateResource(stateResource, () => (this.fileList = [...this.fileList, stateResource.resource])); } public deleteAttachment(binaryFile: BinaryFileResource): void { diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.html b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.html index b6efd87e00828c83f8bb8b4a0ff6d3dfb509fa41..de2d67af94b520ec15bb8292ef9278cb78d8e395 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.html +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.html @@ -23,8 +23,7 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<ods-attachment-wrapper> - <alfa-binary-file-list - [binaryFileListStateResource]="binaryFileListStateResource$ | async" - ></alfa-binary-file-list> -</ods-attachment-wrapper> +<alfa-binary-file-list + [binaryFileListStateResource]="binaryFileListStateResource$ | async" + [listOrientation]="listOrientation" +></alfa-binary-file-list> diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.spec.ts b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.spec.ts index e9473e9ee486bc437328ee6fe85227d12c6293e5..6cca0983e8268484b83772fa1f78dafda782e70e 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.spec.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.spec.ts @@ -34,27 +34,19 @@ import { of } from 'rxjs'; import { BinaryFileListContainerComponent } from './binary-file-list-container.component'; import { BinaryFileListComponent } from './binary-file-list/binary-file-list.component'; -import { AttachmentWrapperComponent } from '@ods/system'; - describe('BinaryFileListContainerComponent', () => { let component: BinaryFileListContainerComponent; let fixture: ComponentFixture<BinaryFileListContainerComponent>; const binaryFileService: Mock<BinaryFileService> = mock(BinaryFileService); - const binaryFileStateResource: StateResource<BinaryFileResource> = createStateResource( - createBinaryFileResource(), - ); + const binaryFileStateResource: StateResource<BinaryFileResource> = createStateResource(createBinaryFileResource()); const resource: Resource = createDummyResource(); const linkRel: LinkRelationName = DummyLinkRel.DUMMY; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - BinaryFileListContainerComponent, - MockComponent(BinaryFileListComponent), - MockComponent(AttachmentWrapperComponent), - ], + declarations: [BinaryFileListContainerComponent, MockComponent(BinaryFileListComponent)], providers: [ { provide: BinaryFileService, @@ -85,8 +77,10 @@ describe('BinaryFileListContainerComponent', () => { describe('binary file list', () => { it('should be called with binary file state resource', () => { - const binaryFileListComponent: BinaryFileListComponent = - getMockComponent<BinaryFileListComponent>(fixture, BinaryFileListComponent); + const binaryFileListComponent: BinaryFileListComponent = getMockComponent<BinaryFileListComponent>( + fixture, + BinaryFileListComponent, + ); expect(binaryFileListComponent.binaryFileListStateResource).toBe(binaryFileStateResource); }); diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.ts b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.ts index 4515dda8f1d00fb992b3b613e8dc9ab79360bf91..b4dce3b40b8590f8037ad669d5469f6c88942f93 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list-container.component.ts @@ -21,15 +21,12 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - BinaryFileListLinkRel, - BinaryFileListResource, - BinaryFileService, -} from '@alfa-client/binary-file-shared'; +import { BinaryFileListResource, BinaryFileService } from '@alfa-client/binary-file-shared'; import { LinkRelationName, StateResource } from '@alfa-client/tech-shared'; import { Component, Input, OnInit } from '@angular/core'; import { Resource } from '@ngxp/rest'; import { Observable } from 'rxjs'; +import { BinaryFileListOrientation } from '../directive/binary-file-list-orientation/binary-file-list-orientation.directive'; @Component({ selector: 'alfa-binary-file-list-container', @@ -38,11 +35,10 @@ import { Observable } from 'rxjs'; export class BinaryFileListContainerComponent implements OnInit { @Input() resource: Resource; @Input() linkRel: LinkRelationName; + @Input() listOrientation: BinaryFileListOrientation = BinaryFileListOrientation.HORIZONTAL; public binaryFileListStateResource$: Observable<StateResource<BinaryFileListResource>>; - public readonly binaryFileListLinkRel = BinaryFileListLinkRel; - constructor(private service: BinaryFileService) {} ngOnInit(): void { diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.html b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.html index ca935c68da85787e1a6404452d9be4dd0c466cbf..a4ee6adc69c75525b8a32dd2e0ad5a9352701c2d 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.html +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.html @@ -23,12 +23,13 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<alfa-binary-file2-container - *ngFor=" - let binaryFile of binaryFileListStateResource.resource - | toEmbeddedResources: binaryFileListLinkRel.FILE_LIST - " - [file]="binaryFile" - [deletable]="false" -> -</alfa-binary-file2-container> +@if (binaryFileListStateResource.resource | toEmbeddedResources: binaryFileListLinkRel.FILE_LIST; as binaryFileList) { + @if (binaryFileList.length) { + <ods-attachment-wrapper data-test-id="binary-file-list-wrapper"> + <div [binaryFileListOrientation]="listOrientation"> + <alfa-binary-file2-container *ngFor="let binaryFile of binaryFileList" [file]="binaryFile" [deletable]="false"> + </alfa-binary-file2-container> + </div> + </ods-attachment-wrapper> + } +} diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.spec.ts b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.spec.ts index b90c651f44cba9b96f13aec0f6dec08348572bba..354a9d2be51cb3f1bd9bdb20d1525ed75b653904 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.spec.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.spec.ts @@ -22,19 +22,15 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BinaryFileListResource, BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { - StateResource, - ToEmbeddedResourcesPipe, - createStateResource, -} from '@alfa-client/tech-shared'; -import { getMockComponent } from '@alfa-client/test-utils'; +import { createStateResource, StateResource, ToEmbeddedResourcesPipe } from '@alfa-client/tech-shared'; +import { existsAsHtmlElement, getMockComponent, notExistsAsHtmlElement } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { - createBinaryFileListResource, - createBinaryFileResource, -} from 'libs/binary-file-shared/test/binary-file'; -import { MockComponent } from 'ng-mocks'; +import { AttachmentWrapperComponent } from '@ods/system'; +import { createBinaryFileListResource, createBinaryFileResource } from 'libs/binary-file-shared/test/binary-file'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent, MockDirective } from 'ng-mocks'; import { BinaryFile2ContainerComponent } from '../../binary-file2-container/binary-file2-container.component'; +import { BinaryFileListOrientationDirective } from '../../directive/binary-file-list-orientation/binary-file-list-orientation.directive'; import { BinaryFileListComponent } from './binary-file-list.component'; describe('BinaryFileListComponent', () => { @@ -42,9 +38,11 @@ describe('BinaryFileListComponent', () => { let fixture: ComponentFixture<BinaryFileListComponent>; const binaryFile: BinaryFileResource = createBinaryFileResource(); - const binaryFileListStateResource: StateResource<BinaryFileListResource> = createStateResource( - createBinaryFileListResource([binaryFile]), - ); + function getBinaryFileListStateResource(binaryFiles: BinaryFileResource[]): StateResource<BinaryFileListResource> { + return createStateResource(createBinaryFileListResource(binaryFiles)); + } + + const wrapperSelector: string = getDataTestIdOf('binary-file-list-wrapper'); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -52,12 +50,14 @@ describe('BinaryFileListComponent', () => { BinaryFileListComponent, ToEmbeddedResourcesPipe, MockComponent(BinaryFile2ContainerComponent), + MockComponent(AttachmentWrapperComponent), + MockDirective(BinaryFileListOrientationDirective), ], }).compileComponents(); fixture = TestBed.createComponent(BinaryFileListComponent); component = fixture.componentInstance; - component.binaryFileListStateResource = binaryFileListStateResource; + component.binaryFileListStateResource = getBinaryFileListStateResource([binaryFile]); fixture.detectChanges(); }); @@ -65,17 +65,37 @@ describe('BinaryFileListComponent', () => { expect(component).toBeTruthy(); }); + describe('template', () => { + describe('attachment wrapper', () => { + it('should show', () => { + existsAsHtmlElement(fixture, wrapperSelector); + }); + + it('should hide', () => { + component.binaryFileListStateResource = getBinaryFileListStateResource([]); + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, wrapperSelector); + }); + }); + }); + describe('binary file container', () => { it('should be called with file', () => { - const binaryFileContainerComponent: BinaryFile2ContainerComponent = - getMockComponent<BinaryFile2ContainerComponent>(fixture, BinaryFile2ContainerComponent); + const binaryFileContainerComponent: BinaryFile2ContainerComponent = getMockComponent<BinaryFile2ContainerComponent>( + fixture, + BinaryFile2ContainerComponent, + ); expect(binaryFileContainerComponent.file).toBe(binaryFile); }); it('should be called with deleteable', () => { - const binaryFileContainerComponent: BinaryFile2ContainerComponent = - getMockComponent<BinaryFile2ContainerComponent>(fixture, BinaryFile2ContainerComponent); + const binaryFileContainerComponent: BinaryFile2ContainerComponent = getMockComponent<BinaryFile2ContainerComponent>( + fixture, + BinaryFile2ContainerComponent, + ); expect(binaryFileContainerComponent.deletable).toBeFalsy(); }); diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.ts b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.ts index 33f6ef5e25a5b1a1dc06033c9354befc355cf204..f0ddb940344f0afeabe9b855b04a1d189b9f2840 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file-list-container/binary-file-list/binary-file-list.component.ts @@ -24,6 +24,7 @@ import { BinaryFileListLinkRel, BinaryFileListResource } from '@alfa-client/binary-file-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { Component, Input } from '@angular/core'; +import { BinaryFileListOrientation } from '../../directive/binary-file-list-orientation/binary-file-list-orientation.directive'; @Component({ selector: 'alfa-binary-file-list', @@ -31,6 +32,7 @@ import { Component, Input } from '@angular/core'; }) export class BinaryFileListComponent { @Input() public binaryFileListStateResource: StateResource<BinaryFileListResource>; + @Input() listOrientation: BinaryFileListOrientation = BinaryFileListOrientation.HORIZONTAL; public readonly binaryFileListLinkRel = BinaryFileListLinkRel; } diff --git a/alfa-client/libs/binary-file/src/lib/binary-file.module.ts b/alfa-client/libs/binary-file/src/lib/binary-file.module.ts index 9327cbb0be7ef4527eff2aaf36b8f4ffac6e0cb3..362e1cbf5dc20d32ce56b18adbafad5e0ecd7b65 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file.module.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file.module.ts @@ -28,14 +28,7 @@ import { NgModule } from '@angular/core'; import { MatIcon } from '@angular/material/icon'; import { MatTooltip } from '@angular/material/tooltip'; import { DownloadButtonComponent } from '@ods/component'; -import { - AttachmentComponent, - AttachmentHeaderComponent, - AttachmentWrapperComponent, - CloseIconComponent, - SpinnerIconComponent, - TooltipDirective, -} from '@ods/system'; +import { AttachmentComponent, AttachmentHeaderComponent, AttachmentWrapperComponent, CloseIconComponent, SpinnerIconComponent, TooltipDirective, } from '@ods/system'; import { FileSizePlainPipe } from '../../../tech-shared/src/lib/pipe/file-size-plain.pipe'; import { FileUploadEditorComponent } from '../../../ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component'; import { BinaryFileAttachmentContainerComponent } from './binary-file-attachment-container/binary-file-attachment-container.component'; @@ -46,6 +39,7 @@ import { BinaryFileListComponent } from './binary-file-list-container/binary-fil import { BinaryFileUriContainerComponent } from './binary-file-uri-container/binary-file-uri-container.component'; import { BinaryFile2ContainerComponent } from './binary-file2-container/binary-file2-container.component'; import { BinaryFile2Component } from './binary-file2-container/binary-file2/binary-file2.component'; +import { BinaryFileListOrientationDirective } from './directive/binary-file-list-orientation/binary-file-list-orientation.directive'; import { DownloadArchiveFileButtonContainerComponent } from './download-archive-file-button-container/download-archive-file-button-container.component'; import { HorizontalBinaryFileListComponent } from './horizontal-binary-file-list/horizontal-binary-file-list.component'; import { VerticalBinaryFileListComponent } from './vertical-binary-file-list/vertical-binary-file-list.component'; @@ -70,6 +64,7 @@ import { VerticalBinaryFileListComponent } from './vertical-binary-file-list/ver ToEmbeddedResourcesPipe, FileSizePlainPipe, TooltipDirective, + BinaryFileListOrientationDirective, ], declarations: [ BinaryFileAttachmentContainerComponent, diff --git a/alfa-client/libs/binary-file/src/lib/binary-file2-container/binary-file2/binary-file2.component.html b/alfa-client/libs/binary-file/src/lib/binary-file2-container/binary-file2/binary-file2.component.html index 96927c6b043705cd333e9783282e98ceeca9bc79..28d0e68be052035a9f7b7ce8cdbb940fb3decda2 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file2-container/binary-file2/binary-file2.component.html +++ b/alfa-client/libs/binary-file/src/lib/binary-file2-container/binary-file2/binary-file2.component.html @@ -40,6 +40,7 @@ (click)="deleteFile($event)" title="Anhang löschen" aria-label="Anhang löschen Button" + data-test-class="delete-file-button" > <ods-close-icon class="fill-text"></ods-close-icon> </button> diff --git a/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.spec.ts b/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..82a862b8793d0ae55026acbcc19c83488eb0d610 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.spec.ts @@ -0,0 +1,64 @@ +import { expect } from '@jest/globals'; +import { + _horizontalClasses, + _verticalClasses, + BinaryFileListOrientation, + BinaryFileListOrientationDirective, +} from './binary-file-list-orientation.directive'; + +describe('BinaryFileListOrientationDirective', () => { + let directive: BinaryFileListOrientationDirective; + let elementRef: any = {}; + + beforeEach(() => { + elementRef = { + nativeElement: { + classList: { + add: jest.fn(), + }, + }, + }; + directive = new BinaryFileListOrientationDirective(elementRef); + }); + + describe('set binaryFileListOrientation', () => { + beforeEach(() => { + directive._evaluateClasses = jest.fn().mockReturnValue([]); + }); + + it('should evaluate classes', () => { + directive.binaryFileListOrientation = BinaryFileListOrientation.HORIZONTAL; + + expect(directive._evaluateClasses).toHaveBeenCalledWith(BinaryFileListOrientation.HORIZONTAL); + }); + + it('should add classes', () => { + const classes: string[] = ['test']; + directive._evaluateClasses = jest.fn().mockReturnValue(classes); + + directive.binaryFileListOrientation = BinaryFileListOrientation.HORIZONTAL; + + expect(elementRef.nativeElement.classList.add).toHaveBeenCalledWith('test'); + }); + }); + + describe('_evaluateClasses', () => { + it('should return horizontal classes', () => { + const classes: string[] = directive._evaluateClasses(BinaryFileListOrientation.HORIZONTAL); + + expect(classes).toEqual(_horizontalClasses); + }); + + it('should return vertical classes', () => { + const classes: string[] = directive._evaluateClasses(BinaryFileListOrientation.VERTICAL); + + expect(classes).toEqual(_verticalClasses); + }); + + it('should return horizontal classes by default', () => { + const classes: string[] = directive._evaluateClasses(null); + + expect(classes).toEqual(_horizontalClasses); + }); + }); +}); diff --git a/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.ts b/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..956be5722cdcd4966e12c8a5216bfb0dde53d706 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive.ts @@ -0,0 +1,32 @@ +import { Directive, ElementRef, Input } from '@angular/core'; + +export const _verticalClasses: string[] = ['flex', 'flex-col']; +export const _horizontalClasses: string[] = ['flex', 'flex-wrap', 'gap-2']; + +export enum BinaryFileListOrientation { + HORIZONTAL = 'horizontal', + VERTICAL = 'vertical', +} + +@Directive({ + selector: '[binaryFileListOrientation]', + standalone: true, +}) +export class BinaryFileListOrientationDirective { + @Input() set binaryFileListOrientation(orientation: BinaryFileListOrientation) { + this.el.nativeElement.classList.add(...this._evaluateClasses(orientation)); + } + + constructor(private readonly el: ElementRef) {} + + _evaluateClasses(orientation: BinaryFileListOrientation): string[] { + switch (orientation) { + case BinaryFileListOrientation.VERTICAL: + return _verticalClasses; + case BinaryFileListOrientation.HORIZONTAL: + return _horizontalClasses; + default: + return _horizontalClasses; + } + } +} diff --git a/alfa-client/libs/binary-file/src/lib/download-archive-file-button-container/download-archive-file-button-container.component.html b/alfa-client/libs/binary-file/src/lib/download-archive-file-button-container/download-archive-file-button-container.component.html index b4f57dff63600c9090de98d2646272bad58e06f4..9434cdd4a3edbaa2d3adb37bc41b54d585dc1b81 100644 --- a/alfa-client/libs/binary-file/src/lib/download-archive-file-button-container/download-archive-file-button-container.component.html +++ b/alfa-client/libs/binary-file/src/lib/download-archive-file-button-container/download-archive-file-button-container.component.html @@ -24,6 +24,7 @@ --> <ods-download-button + dataTestId="download-archive-file-button" [stateResource]="downloadArchiveInProgress$ | async" data-test-class="download-archive" (click)="downloadArchive()" diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.html b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e95763af4b9cb08c7b3164618e4910199d2dafd5 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.html @@ -0,0 +1,6 @@ +<ods-file-upload-list + [uploadedFiles]="uploadedFiles$ | async" + [parentFormArrayName]="parentFormArrayName" + [listOrientation]="listOrientation" + (delete)="onDelete($event)" +></ods-file-upload-list> diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.spec.ts b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ccf5fe67622d5cfa056b44b26d29ceed6f4f09ef --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.spec.ts @@ -0,0 +1,160 @@ +import { BinaryFileListLinkRel, BinaryFileListResource, BinaryFileService, UploadFile } from '@alfa-client/binary-file-shared'; +import { createStateResource, getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; +import { mock, Mock } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker'; +import { expect } from '@jest/globals'; +import { Resource } from '@ngxp/rest'; +import { cold } from 'jest-marbles'; +import { MockComponent } from 'ng-mocks'; +import { EMPTY, of } from 'rxjs'; +import { createBinaryFileListResource, createUploadFile } from '../../../../binary-file-shared/test/binary-file'; +import { DummyLinkRel } from '../../../../tech-shared/test/dummy'; +import { singleColdCompleted } from '../../../../tech-shared/test/marbles'; +import { createDummyResource } from '../../../../tech-shared/test/resource'; +import { BinaryFileListOrientation } from '../directive/binary-file-list-orientation/binary-file-list-orientation.directive'; +import { FileUploadListContainerComponent } from './file-upload-list-container.component'; +import { FileUploadListComponent } from './file-upload-list/file-upload-list.component'; + +describe('FileUploadListContainerComponent', () => { + let component: FileUploadListContainerComponent; + let fixture: ComponentFixture<FileUploadListContainerComponent>; + + let binaryFileService: Mock<BinaryFileService>; + + beforeEach(() => { + binaryFileService = mock(BinaryFileService); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FileUploadListContainerComponent], + declarations: [MockComponent(FileUploadListComponent)], + providers: [{ provide: BinaryFileService, useValue: binaryFileService }], + }).compileComponents(); + + fixture = TestBed.createComponent(FileUploadListContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('component', () => { + it('should have default value', () => { + expect(component.listOrientation).toEqual(BinaryFileListOrientation.HORIZONTAL); + }); + + describe('set files resource', () => { + beforeEach(() => { + component._initFileList = jest.fn(); + }); + + it('should set value', () => { + const resource: Resource = createDummyResource(); + + component.filesResource = resource; + + expect(component._filesResource).toEqual(resource); + }); + + it('should init file list', () => { + component.filesResource = createDummyResource(); + + expect(component._initFileList).toHaveBeenCalled(); + }); + }); + + describe('_initFileList', () => { + beforeEach(() => { + binaryFileService.getFiles.mockReturnValue(EMPTY); + }); + + describe('on existing files', () => { + const filesLinkRel = DummyLinkRel.DUMMY; + const filesResource: Resource = createDummyResource([filesLinkRel]); + + beforeEach(() => { + component.filesResource = filesResource; + component.filesLinkRel = filesLinkRel; + component.fileUploadType = faker.word.noun(); + }); + + it('should load existing files', () => { + component.filesResource = filesResource; + component.filesLinkRel = filesLinkRel; + + component._initFileList(); + + expect(binaryFileService.getFiles).toHaveBeenCalledWith(filesResource, filesLinkRel); + }); + + it('should add loaded files', () => { + const stateResource: StateResource<BinaryFileListResource> = createStateResource(createBinaryFileListResource()); + binaryFileService.getFiles.mockReturnValue(of(stateResource)); + + component._initFileList(); + component.uploadedFiles$.subscribe(); + + expect(binaryFileService.addFiles).toHaveBeenCalledWith( + component.fileUploadType, + getEmbeddedResources(stateResource, BinaryFileListLinkRel.FILE_LIST), + ); + }); + + it('should get uploaded files', () => { + const stateResource: StateResource<BinaryFileListResource> = createStateResource(createBinaryFileListResource()); + binaryFileService.getFiles.mockReturnValue(of(stateResource)); + + component._initFileList(); + component.uploadedFiles$.subscribe(); + + expect(binaryFileService.getUploadedFiles).toHaveBeenCalledWith(component.fileUploadType); + }); + + it('should emit values', () => { + const stateResource: StateResource<BinaryFileListResource> = createStateResource(createBinaryFileListResource()); + binaryFileService.getFiles.mockReturnValue(cold('-a', { a: stateResource })); + const uploadedFiles: UploadFile[] = [createUploadFile()]; + binaryFileService.getUploadedFiles.mockReturnValue(cold('-a', { a: uploadedFiles })); + + component._initFileList(); + + expect(component.uploadedFiles$).toBeObservable(cold('a-b', { a: {}, b: uploadedFiles })); + }); + }); + + describe('on none existing files', () => { + const filesResource: Resource = createDummyResource(); + + beforeEach(() => { + component.filesResource = filesResource; + component.filesLinkRel = DummyLinkRel.DUMMY; + }); + + it('should NOT load existing files', () => { + component._initFileList(); + + expect(binaryFileService.getFiles).not.toHaveBeenCalled(); + }); + + it('should get uploaded files', () => { + component._initFileList(); + + expect(binaryFileService.getUploadedFiles).toHaveBeenCalledWith(component.fileUploadType); + }); + + it('should set uploaded files', () => { + const uploadFile: UploadFile = createUploadFile(); + binaryFileService.getUploadedFiles = jest.fn().mockReturnValue(singleColdCompleted(uploadFile)); + + component._initFileList(); + + expect(component.uploadedFiles$).toBeObservable(singleColdCompleted(uploadFile)); + }); + }); + }); + }); +}); diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.ts b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..406726ee4de247d678fe64489969016d1f1ed878 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-container.component.ts @@ -0,0 +1,63 @@ +import { + BinaryFileListLinkRel, + BinaryFileListResource, + BinaryFileResource, + BinaryFileService, + UploadFileByIdentifier, +} from '@alfa-client/binary-file-shared'; +import { getEmbeddedResources, isLoaded, StateResource } from '@alfa-client/tech-shared'; +import { AsyncPipe } from '@angular/common'; +import { Component, inject, Input } from '@angular/core'; +import { hasLink, Resource } from '@ngxp/rest'; +import { filter, map, Observable, startWith, switchMap } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { BinaryFileListOrientation } from '../directive/binary-file-list-orientation/binary-file-list-orientation.directive'; +import { FileUploadListComponent } from './file-upload-list/file-upload-list.component'; + +@Component({ + selector: 'ods-file-upload-list-container', + standalone: true, + templateUrl: './file-upload-list-container.component.html', + imports: [FileUploadListComponent, AsyncPipe], +}) +export class FileUploadListContainerComponent { + @Input() fileUploadType: string; + @Input() parentFormArrayName: string; + @Input() listOrientation: BinaryFileListOrientation = BinaryFileListOrientation.HORIZONTAL; + + @Input() set filesResource(value: Resource) { + this._filesResource = value; + this._initFileList(); + } + + @Input() set filesLinkRel(value: string) { + this._filesLinkRel = value; + this._initFileList(); + } + + private readonly binaryFileService: BinaryFileService = inject(BinaryFileService); + + public uploadedFiles$: Observable<UploadFileByIdentifier>; + _filesResource: Resource; + _filesLinkRel: string; + + _initFileList(): void { + if (hasLink(this._filesResource, this._filesLinkRel)) { + this.uploadedFiles$ = this.binaryFileService.getFiles(this._filesResource, this._filesLinkRel).pipe( + filter(isLoaded), + map((files: StateResource<BinaryFileListResource>) => getEmbeddedResources(files, BinaryFileListLinkRel.FILE_LIST)), + tap((binaryFileResources: BinaryFileResource[]) => + this.binaryFileService.addFiles(this.fileUploadType, binaryFileResources), + ), + switchMap(() => this.binaryFileService.getUploadedFiles(this.fileUploadType)), + startWith({}), + ); + } else { + this.uploadedFiles$ = this.binaryFileService.getUploadedFiles(this.fileUploadType); + } + } + + public onDelete(key: string): void { + this.binaryFileService.deleteUploadedFile(this.fileUploadType, key); + } +} diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.html b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.html new file mode 100644 index 0000000000000000000000000000000000000000..c6e0aa4b1713a6ba70cd5fa9ae98d8d5265c1f5d --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.html @@ -0,0 +1,20 @@ +@if (uploadFile.uploadedFile.loading || uploadFile.uploadedFile.error) { + <ods-attachment + [loadingCaption]="uploadFile.fileToUpload.name" + errorCaption="Fehler beim Hochladen" + [errorMessages]="uploadFile.uploadedFile.error | convertProblemDetailToErrorMessages" + description="Anhang wird hochgeladen" + [isLoading]="uploadFile.uploadedFile.loading" + [attr.data-test-id]="(uploadFile.fileToUpload.name | convertForDataTest) + '-file-upload-list-item-attachment-upload'" + ></ods-attachment> +} @else if (uploadFile.uploadedFile.resource) { + <ods-attachment-wrapper> + <alfa-binary-file2-container + [file]="uploadFile.uploadedFile.resource" + [deletable]="true" + (startDelete)="delete.emit({ key, binaryFileResource: $event })" + [attr.data-test-id]="(uploadFile.uploadedFile.resource.name | convertForDataTest) + '-file-upload-list-item-uploaded'" + > + </alfa-binary-file2-container> + </ods-attachment-wrapper> +} diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.spec.ts b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b1321620c75ccdb5cfeba566b46abdaead8fb95 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.spec.ts @@ -0,0 +1,156 @@ +import { BinaryFile2ContainerComponent, BinaryFileModule } from '@alfa-client/binary-file'; +import { BinaryFileResource } from '@alfa-client/binary-file-shared'; +import { + convertForDataTest, + ConvertForDataTestPipe, + ConvertProblemDetailToErrorMessagesPipe, + createEmptyStateResource, + createErrorStateResource, + createStateResource, +} from '@alfa-client/tech-shared'; +import { + existsAsHtmlElement, + getElementComponentFromFixtureByCss, + notExistsAsHtmlElement, + triggerEvent, +} from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { expect } from '@jest/globals'; +import { AttachmentComponent } from '@ods/system'; +import { MockComponent, MockModule } from 'ng-mocks'; +import { + createBinaryFileResource, + createFile, + createLoadingBinaryFileStateResource, +} from '../../../../../binary-file-shared/test/binary-file'; +import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test'; +import { createProblemDetail } from '../../../../../tech-shared/test/error'; +import { FileUploadListItemComponent } from './file-upload-list-item.component'; + +describe('FileUploadListItemComponent', () => { + let component: FileUploadListItemComponent; + let fixture: ComponentFixture<FileUploadListItemComponent>; + + const file: File = createFile(); + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + const attachmentTestId: string = getDataTestIdOf(convertForDataTest(file.name) + '-file-upload-list-item-attachment-upload'); + const binaryFileContainerTestId: string = getDataTestIdOf( + convertForDataTest(binaryFileResource.name) + '-file-upload-list-item-uploaded', + ); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FileUploadListItemComponent, ConvertProblemDetailToErrorMessagesPipe, ConvertForDataTestPipe], + declarations: [MockModule(BinaryFileModule), MockComponent(BinaryFile2ContainerComponent)], + }).compileComponents(); + + fixture = TestBed.createComponent(FileUploadListItemComponent); + component = fixture.componentInstance; + component.uploadFile = { uploadedFile: createEmptyStateResource() }; + fixture.detectChanges(); + }); + + describe('component', () => { + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + beforeEach(() => { + component.uploadFile.fileToUpload = file; + }); + + describe('attachment upload', () => { + it('should exists on loading', () => { + component.uploadFile.uploadedFile = createLoadingBinaryFileStateResource(); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, attachmentTestId); + }); + + it('should exists on error', () => { + component.uploadFile.uploadedFile = createErrorStateResource(createProblemDetail()); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, attachmentTestId); + }); + + it('should NOT exists on loaded', () => { + component.uploadFile.uploadedFile = createStateResource(binaryFileResource); + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, attachmentTestId); + }); + + it('should have inputs', () => { + component.uploadFile.fileToUpload = file; + component.uploadFile.uploadedFile = createEmptyStateResource(true); + + fixture.detectChanges(); + + const attachmentComponent: AttachmentComponent = getElementComponentFromFixtureByCss(fixture, attachmentTestId); + + expect(attachmentComponent.loadingCaption).toEqual(component.uploadFile.fileToUpload.name); + expect(attachmentComponent.errorMessages).toEqual([]); + expect(attachmentComponent.isLoading).toEqual(true); + }); + }); + + describe('uploaded file', () => { + it('should exists', () => { + component.uploadFile.uploadedFile = createStateResource(binaryFileResource); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, binaryFileContainerTestId); + }); + + it('should NOT exists', () => { + component.uploadFile.uploadedFile = createEmptyStateResource(true); + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, binaryFileContainerTestId); + }); + + it('should have inputs', () => { + component.uploadFile.uploadedFile = createStateResource(binaryFileResource); + + fixture.detectChanges(); + + const binaryFileComponent: BinaryFile2ContainerComponent = getElementComponentFromFixtureByCss( + fixture, + binaryFileContainerTestId, + ); + + expect(binaryFileComponent.file).toEqual(component.uploadFile.uploadedFile.resource); + expect(binaryFileComponent.deletable).toEqual(true); + }); + + describe('output', () => { + describe('startDelete', () => { + it('should emit', () => { + component.delete.emit = jest.fn(); + component.key = 'test'; + component.uploadFile.uploadedFile = createStateResource(binaryFileResource); + + fixture.detectChanges(); + + triggerEvent({ + fixture, + elementSelector: binaryFileContainerTestId, + name: 'startDelete', + data: binaryFileResource, + }); + + expect(component.delete.emit).toHaveBeenCalledWith({ key: component.key, binaryFileResource }); + }); + }); + }); + }); + }); + }); +}); diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.ts b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..854f6f0466ac5d5f8589c244f751ce2493e82f76 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list-item/file-upload-list-item.component.ts @@ -0,0 +1,24 @@ +import { BinaryFileModule } from '@alfa-client/binary-file'; +import { FileToDelete, UploadFile } from '@alfa-client/binary-file-shared'; +import { ConvertForDataTestPipe, ConvertProblemDetailToErrorMessagesPipe } from '@alfa-client/tech-shared'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { AttachmentComponent, AttachmentWrapperComponent } from '@ods/system'; + +@Component({ + selector: 'ods-file-upload-list-item', + standalone: true, + templateUrl: './file-upload-list-item.component.html', + imports: [ + AttachmentComponent, + AttachmentWrapperComponent, + BinaryFileModule, + ConvertProblemDetailToErrorMessagesPipe, + ConvertForDataTestPipe, + ], +}) +export class FileUploadListItemComponent { + @Input() key: string; + @Input() uploadFile: UploadFile; + + @Output() delete: EventEmitter<FileToDelete> = new EventEmitter(); +} diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.html b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.html new file mode 100644 index 0000000000000000000000000000000000000000..b29089f98c4488b9be682850a39331f2cd46a607 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.html @@ -0,0 +1,9 @@ +<div [binaryFileListOrientation]="listOrientation"> + @for (uploadItem of uploadItems | keyvalue; track uploadItem.key) { + <ods-file-upload-list-item + [key]="uploadItem.key" + [uploadFile]="uploadItem.value" + (delete)="onDelete($event)" + ></ods-file-upload-list-item> + } +</div> diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.spec.ts b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3405027b7836f6a23dcd63d43f2bf00dbb7c07a5 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.spec.ts @@ -0,0 +1,179 @@ +import { BinaryFileResource, UploadFileByIdentifier } from '@alfa-client/binary-file-shared'; +import { createEmptyStateResource, createStateResource, StateResource } from '@alfa-client/tech-shared'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormGroupDirective, UntypedFormArray, UntypedFormBuilder, UntypedFormControl } from '@angular/forms'; +import { faker } from '@faker-js/faker/.'; +import { getUrl } from '@ngxp/rest'; +import { createBinaryFileResource, createUploadFile } from 'libs/binary-file-shared/test/binary-file'; +import { FileUploadListComponent } from './file-upload-list.component'; + +describe('FileUploadListComponent', () => { + let component: FileUploadListComponent; + let fixture: ComponentFixture<FileUploadListComponent>; + + const fb: UntypedFormBuilder = new UntypedFormBuilder(); + const formGroupDirective: FormGroupDirective = new FormGroupDirective([], []); + formGroupDirective.form = fb.group({ + attachments: fb.control(null), + }); + + const fileKey: string = '21'; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FileUploadListComponent], + providers: [ + { + provide: FormGroupDirective, + useValue: formGroupDirective, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(FileUploadListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('component', () => { + describe('set uploaded files', () => { + it('should update upload items', () => { + component._updateUploadItems = jest.fn(); + const uploadedFiles: UploadFileByIdentifier = { [fileKey]: createUploadFile() }; + + component.uploadedFiles = uploadedFiles; + + expect(component._updateUploadItems).toHaveBeenCalledWith(uploadedFiles); + }); + }); + + describe('ngOnInit', () => { + it('should set file link controls', () => { + component.parentFormArrayName = 'attachments'; + + component.ngOnInit(); + + expect(component._fileLinkControls).toEqual(formGroupDirective.form.get('attachments')); + }); + }); + + describe('_updateUploadItems', () => { + beforeEach(() => { + component._addFileUrl = jest.fn(); + component._updateForm = jest.fn(); + }); + + it('should set upload items', () => { + const uploadedFiles: UploadFileByIdentifier = { [fileKey]: createUploadFile() }; + + component._updateUploadItems(uploadedFiles); + + expect(component.uploadItems).toEqual(uploadedFiles); + }); + + it('should set file urls', () => { + const uploadStateResource: StateResource<BinaryFileResource> = createStateResource(createBinaryFileResource()); + const uploadedFiles: UploadFileByIdentifier = { [fileKey]: { ...createUploadFile(), uploadedFile: uploadStateResource } }; + + component._updateUploadItems(uploadedFiles); + + expect(component._fileUrls).toEqual([getUrl(uploadStateResource.resource)]); + }); + + it('should NOT add file url on loading', () => { + const uploadedFiles: UploadFileByIdentifier = { [fileKey]: { uploadedFile: createEmptyStateResource(true) } }; + + component._updateUploadItems(uploadedFiles); + + expect(component._fileUrls).toEqual([]); + }); + + it('should update form', () => { + const uploadedFiles: UploadFileByIdentifier = { [fileKey]: { uploadedFile: createEmptyStateResource(true) } }; + + component._updateUploadItems(uploadedFiles); + + expect(component._updateForm).toHaveBeenCalled(); + }); + }); + + describe('_addFileUrl', () => { + beforeEach(() => { + component._updateForm = jest.fn(); + }); + + it('should add file url', () => { + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + + component._addFileUrl(binaryFileResource); + + expect(component._fileUrls).toEqual([getUrl(binaryFileResource)]); + }); + + it('should update form', () => { + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + + component._addFileUrl(binaryFileResource); + + expect(component._updateForm).toHaveBeenCalled(); + }); + }); + + describe('_updateForm', () => { + beforeEach(() => { + component._fileLinkControls = new UntypedFormArray([new UntypedFormControl(faker.internet.url())]); + }); + + it('should clear file control list', () => { + component._fileUrls = [faker.internet.url()]; + + component._updateForm(); + + expect(component._fileLinkControls.length).toEqual(1); + }); + it('should update file control list', () => { + const fileUrl: string = faker.internet.url(); + component._fileUrls = [fileUrl]; + + component._updateForm(); + + expect(component._fileLinkControls.controls[0].value).toEqual(fileUrl); + }); + }); + + describe('onDelete', () => { + const binaryFileResource: BinaryFileResource = createBinaryFileResource(); + const fileUrl1: string = faker.internet.url(); + const fileUrl2: string = getUrl(binaryFileResource); + const key: string = faker.word.noun(); + + beforeEach(() => { + component.delete.emit = jest.fn(); + component._fileUrls = [fileUrl1, fileUrl2]; + component._updateForm = jest.fn(); + }); + + it('should update file urls', () => { + component.onDelete({ binaryFileResource, key }); + + expect(component._fileUrls).toEqual([fileUrl1]); + }); + + it('should emit', () => { + component.onDelete({ binaryFileResource, key }); + + expect(component.delete.emit).toHaveBeenCalledWith(key); + }); + + it('should update form', () => { + component.onDelete({ binaryFileResource, key }); + + expect(component._updateForm).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.ts b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..29b1cae5ed2a6ca63e6274a2fff1ddd96bdb212b --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/file-upload-list-container/file-upload-list/file-upload-list.component.ts @@ -0,0 +1,64 @@ +import { BinaryFileResource, FileToDelete, UploadFile, UploadFileByIdentifier } from '@alfa-client/binary-file-shared'; +import { isLoaded, StateResource } from '@alfa-client/tech-shared'; +import { KeyValuePipe } from '@angular/common'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormGroupDirective, UntypedFormArray, UntypedFormControl } from '@angular/forms'; +import { getUrl } from '@ngxp/rest'; +import { + BinaryFileListOrientation, + BinaryFileListOrientationDirective, +} from '../../directive/binary-file-list-orientation/binary-file-list-orientation.directive'; +import { FileUploadListItemComponent } from '../file-upload-list-item/file-upload-list-item.component'; + +@Component({ + selector: 'ods-file-upload-list', + standalone: true, + templateUrl: './file-upload-list.component.html', + imports: [FileUploadListItemComponent, KeyValuePipe, BinaryFileListOrientationDirective], +}) +export class FileUploadListComponent implements OnInit { + @Input() parentFormArrayName: string; + @Input() listOrientation: BinaryFileListOrientation; + + @Input() set uploadedFiles(value: UploadFileByIdentifier) { + this._updateUploadItems(value); + } + + @Output() delete: EventEmitter<string> = new EventEmitter(); + + public uploadItems: UploadFileByIdentifier; + + _fileLinkControls: UntypedFormArray = new UntypedFormArray([]); + _fileUrls: string[] = []; + + constructor(public parentForm: FormGroupDirective) {} + + ngOnInit(): void { + this._fileLinkControls = this.parentForm.form.get(this.parentFormArrayName) as UntypedFormArray; + } + + _updateUploadItems(uploadFiles: UploadFileByIdentifier): void { + this.uploadItems = uploadFiles; + this._fileUrls = Object.values(uploadFiles) + .map((value: UploadFile) => value.uploadedFile) + .filter(isLoaded) + .map((stateResource: StateResource<BinaryFileResource>) => getUrl(stateResource.resource)); + this._updateForm(); + } + + _addFileUrl(binaryFileResource: BinaryFileResource): void { + this._fileUrls = [...this._fileUrls, getUrl(binaryFileResource)]; + this._updateForm(); + } + + _updateForm(): void { + this._fileLinkControls.clear(); + this._fileUrls.forEach((link: string) => this._fileLinkControls.push(new UntypedFormControl(link))); + } + + public onDelete(fileToDelete: FileToDelete): void { + this._fileUrls = this._fileUrls.filter((url: string) => url !== getUrl(fileToDelete.binaryFileResource)); + this._updateForm(); + this.delete.emit(fileToDelete.key); + } +} diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.html b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.html new file mode 100644 index 0000000000000000000000000000000000000000..538932cf346eea42688fc28259a330b1d18fb552 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.html @@ -0,0 +1,16 @@ +<ods-file-upload-button + [id]="uploadButtonId" + [accept]="accept" + [attr.data-test-id]="(label | convertForDataTest) + '-file-upload-button'" + [multi]="true" + [isLoading]="isUploadInProgress$ | async" + [variant]="uploadButtonVariant" + data-test-id="binary-file-upload" + dataTestId="multi-file-upload-button" +> + <ods-spinner-icon spinner size="medium" /> + <ods-attachment-icon icon size="medium" /> + @if (label) { + <p text data-test-id="upload-button-label" class="text-center">{{ label }}</p> + } +</ods-file-upload-button> diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.spec.ts b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..38303a464c9f128eb05963f94df756663567e129 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.spec.ts @@ -0,0 +1,171 @@ +import { BinaryFileService, FileUploadType, ToUploadFile } from '@alfa-client/binary-file-shared'; +import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; +import { + existsAsHtmlElement, + getElementComponentFromFixtureByCss, + mock, + Mock, + notExistsAsHtmlElement, +} from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker/.'; +import { expect } from '@jest/globals'; +import { getUrl, Resource } from '@ngxp/rest'; +import { FileUploadButtonComponent, SpinnerIconComponent, UploadButtonVariants } from '@ods/system'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { of } from 'rxjs'; +import { createFileList } from '../../../../tech-shared/test/file'; +import { singleColdCompleted } from '../../../../tech-shared/test/marbles'; +import { createDummyResource } from '../../../../tech-shared/test/resource'; +import { MultiFileUploadEditorComponent } from './multi-file-upload-editor.component'; + +describe('MultiFileUploadEditorComponent', () => { + let component: MultiFileUploadEditorComponent; + let fixture: ComponentFixture<MultiFileUploadEditorComponent>; + + const fileUploadType: FileUploadType = faker.word.noun(); + const uploadLinkRel: string = faker.word.noun(); + const uploadResource: Resource = createDummyResource([uploadLinkRel]); + + const buttonTestId: string = getDataTestIdOf('Ein_Label-file-upload-button'); + const buttonLabelTestId: string = getDataTestIdOf('upload-button-label'); + + let binaryFileService: Mock<BinaryFileService>; + + beforeEach(() => { + binaryFileService = mock(BinaryFileService); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + MultiFileUploadEditorComponent, + ConvertForDataTestPipe, + MockComponent(SpinnerIconComponent), + MockComponent(FileUploadButtonComponent), + ], + providers: [ + { + provide: BinaryFileService, + useValue: binaryFileService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(MultiFileUploadEditorComponent); + component = fixture.componentInstance; + component.uploadResource = uploadResource; + component.uploadLinkRelation = uploadLinkRel; + component.label = 'Ein Label'; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('component', () => { + describe('ngOnInit', () => { + it('should get upload in progress', () => { + component.fileUploadType = fileUploadType; + + component.ngOnInit(); + + expect(binaryFileService.isUploadInProgress).toHaveBeenCalledWith(fileUploadType); + }); + + it('should set upload in progress', () => { + binaryFileService.isUploadInProgress = jest.fn().mockReturnValue(of(true)); + + component.ngOnInit(); + + expect(component.isUploadInProgress$).toBeObservable(singleColdCompleted(true)); + }); + }); + + describe('onFilesUpload', () => { + beforeEach(() => { + component._uploadFiles = jest.fn(); + }); + + it('should upload files', () => { + const fileList: FileList = createFileList(); + + component.onFilesUpload(fileList); + + expect(component._uploadFiles).toHaveBeenCalledWith(fileList); + }); + }); + + describe('_uploadFiles', () => { + it('should call binary file service', () => { + const fileList: FileList = createFileList(); + + component._uploadFiles(fileList); + + expect(binaryFileService.uploadFileNew).toHaveBeenCalledWith({ + file: fileList.item(0), + type: component.fileUploadType, + uploadUrl: getUrl(uploadResource, uploadLinkRel), + } as ToUploadFile); + }); + }); + + describe('get uploadButtonVariant', () => { + it('should return "label"', () => { + component.label = 'test'; + + const result: UploadButtonVariants['variant'] = component.uploadButtonVariant; + + expect(result).toBe('label'); + }); + + it('should return "icon"', () => { + component.label = ''; + + const result: UploadButtonVariants['variant'] = component.uploadButtonVariant; + + expect(result).toBe('icon'); + }); + }); + }); + + describe('template', () => { + describe('upload button', () => { + it('should exists', () => { + existsAsHtmlElement(fixture, buttonTestId); + }); + + it('should have inputs', () => { + binaryFileService.isUploadInProgress = jest.fn().mockReturnValue(of(true)); + component.ngOnInit(); + + fixture.detectChanges(); + const fileButtonComponent: FileUploadButtonComponent = getElementComponentFromFixtureByCss(fixture, buttonTestId); + + expect(fileButtonComponent.id).toEqual(component.uploadButtonId); + expect(fileButtonComponent.accept).toEqual(component.accept); + expect(fileButtonComponent.multi).toEqual(true); + expect(fileButtonComponent.isLoading).toEqual(true); + }); + }); + describe('upload button label', () => { + it('should show', () => { + component.label = 'test'; + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, buttonLabelTestId); + }); + + it('should hide', () => { + component.label = ''; + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, buttonLabelTestId); + }); + }); + }); +}); diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.ts b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a9623612015f5350b39822c605c8faed1903d59 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component.ts @@ -0,0 +1,62 @@ +import { BinaryFileModule } from '@alfa-client/binary-file'; +import { BinaryFileService, FileUploadType } from '@alfa-client/binary-file-shared'; +import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; +import { AsyncPipe } from '@angular/common'; +import { Component, HostListener, inject, Input, OnInit } from '@angular/core'; +import { ControlContainer, FormGroupDirective, ReactiveFormsModule } from '@angular/forms'; +import { getUrl, Resource } from '@ngxp/rest'; +import { AttachmentIconComponent, FileUploadButtonComponent, SpinnerIconComponent, UploadButtonVariants } from '@ods/system'; +import { uniqueId } from 'lodash-es'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'ods-multi-file-upload-editor', + templateUrl: './multi-file-upload-editor.component.html', + viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], + standalone: true, + imports: [ + AsyncPipe, + FileUploadButtonComponent, + AttachmentIconComponent, + SpinnerIconComponent, + ReactiveFormsModule, + BinaryFileModule, + ConvertForDataTestPipe, + ], +}) +export class MultiFileUploadEditorComponent implements OnInit { + @Input() label: string = ''; + @Input() accept: string = '*/*'; + @Input() fileUploadType: FileUploadType; + @Input() uploadResource: Resource; + @Input() uploadLinkRelation: string; + + private readonly binaryFileService: BinaryFileService = inject(BinaryFileService); + + public isUploadInProgress$: Observable<boolean>; + + public readonly uploadButtonId: string = uniqueId(); + + ngOnInit(): void { + this.isUploadInProgress$ = this.binaryFileService.isUploadInProgress(this.fileUploadType); + } + + @HostListener('change', ['$event.target.files']) + public onFilesUpload(fileList: FileList): void { + this._uploadFiles(fileList); + } + + _uploadFiles(fileList: FileList) { + for (let i = 0; i < fileList.length; i++) { + this.binaryFileService.uploadFileNew({ + file: fileList.item(i), + type: this.fileUploadType, + uploadUrl: getUrl(this.uploadResource, this.uploadLinkRelation), + }); + } + } + + get uploadButtonVariant(): UploadButtonVariants['variant'] { + return this.label ? 'label' : 'icon'; + } +} diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.html b/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8efd002c007d46dba8fc79c2526e59e132191c66 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.html @@ -0,0 +1,15 @@ +<div class="flex flex-col gap-2"> + <ods-file-upload-list-container + [parentFormArrayName]="filesFormFieldName" + [fileUploadType]="fileUploadType" + [filesResource]="filesResource" + [filesLinkRel]="filesLinkRelation" + data-test-id="file-list" + /> + <ods-multi-file-upload-editor + [fileUploadType]="fileUploadType" + [uploadResource]="uploadResource" + [uploadLinkRelation]="uploadLinkRelation" + data-test-id="multi-file-upload-editor" + /> +</div> diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.spec.ts b/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..70679e7411aabf14b7d08d7bf855a40edc41242b --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.spec.ts @@ -0,0 +1,92 @@ +import { FileUploadListContainerComponent, MultiFileUploadEditorComponent } from '@alfa-client/binary-file'; +import { FileUploadType } from '@alfa-client/binary-file-shared'; +import { existsAsHtmlElement, getElementComponentFromFixtureByCss } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker/.'; +import { expect } from '@jest/globals'; +import { Resource } from '@ngxp/rest'; +import { MockComponent } from 'ng-mocks'; +import { getDataTestIdOf } from '../../../../tech-shared/test/data-test'; +import { DummyLinkRel } from '../../../../tech-shared/test/dummy'; +import { createDummyResource } from '../../../../tech-shared/test/resource'; +import { MultiFileUploadComponent } from './multi-file-upload.component'; + +describe('MultiFileUploadComponent', () => { + let component: MultiFileUploadComponent; + let fixture: ComponentFixture<MultiFileUploadComponent>; + + const fileUploadListTestId: string = getDataTestIdOf('file-list'); + const fileUploadEditorTestId: string = getDataTestIdOf('multi-file-upload-editor'); + + const fileUploadType: FileUploadType = faker.word.noun(); + const uploadLinkRel: string = DummyLinkRel.DUMMY; + const uploadResource: Resource = createDummyResource(); + const filesLinkRel: string = DummyLinkRel.DUMMY; + const filesResource: Resource = createDummyResource(); + const formFieldsName: string = faker.word.noun(); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MultiFileUploadComponent], + declarations: [MockComponent(MultiFileUploadEditorComponent), MockComponent(FileUploadListContainerComponent)], + }).compileComponents(); + + fixture = TestBed.createComponent(MultiFileUploadComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + beforeEach(() => { + component.filesFormFieldName = formFieldsName; + component.fileUploadType = fileUploadType; + component.uploadResource = uploadResource; + component.uploadLinkRelation = uploadLinkRel; + component.filesResource = filesResource; + component.filesLinkRelation = filesLinkRel; + }); + + describe('file upload list', () => { + it('should exists', () => { + existsAsHtmlElement(fixture, fileUploadListTestId); + }); + + it('should have inputs', () => { + fixture.detectChanges(); + + const fileUploadListComponent: FileUploadListContainerComponent = getElementComponentFromFixtureByCss( + fixture, + fileUploadListTestId, + ); + + expect(fileUploadListComponent.parentFormArrayName).toEqual(formFieldsName); + expect(fileUploadListComponent.fileUploadType).toEqual(fileUploadType); + expect(fileUploadListComponent.filesResource).toEqual(filesResource); + expect(fileUploadListComponent.filesLinkRel).toEqual(filesLinkRel); + }); + }); + + describe('file upload editor', () => { + it('should exists', () => { + existsAsHtmlElement(fixture, fileUploadEditorTestId); + }); + + it('should have inputs', () => { + fixture.detectChanges(); + + const fileUploadEditorComponent: MultiFileUploadEditorComponent = getElementComponentFromFixtureByCss( + fixture, + fileUploadEditorTestId, + ); + + expect(fileUploadEditorComponent.fileUploadType).toEqual(fileUploadType); + expect(fileUploadEditorComponent.uploadResource).toEqual(uploadResource); + expect(fileUploadEditorComponent.uploadLinkRelation).toEqual(uploadLinkRel); + }); + }); + }); +}); diff --git a/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.ts b/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..56234cb550a4fdcaf31529f5a0af54457eb94623 --- /dev/null +++ b/alfa-client/libs/binary-file/src/lib/multi-file-upload/multi-file-upload.component.ts @@ -0,0 +1,22 @@ +import { FileUploadType } from '@alfa-client/binary-file-shared'; +import { Component, Input } from '@angular/core'; +import { Resource } from '@ngxp/rest'; +import { BinaryFileListOrientation } from '../directive/binary-file-list-orientation/binary-file-list-orientation.directive'; +import { FileUploadListContainerComponent } from '../file-upload-list-container/file-upload-list-container.component'; +import { MultiFileUploadEditorComponent } from '../multi-file-upload-editor/multi-file-upload-editor.component'; + +@Component({ + selector: 'ods-multi-file-upload', + standalone: true, + templateUrl: './multi-file-upload.component.html', + imports: [FileUploadListContainerComponent, MultiFileUploadEditorComponent], +}) +export class MultiFileUploadComponent { + @Input({ required: true }) filesFormFieldName: string; + @Input({ required: true }) fileUploadType: FileUploadType; + @Input({ required: true }) uploadResource: Resource; + @Input({ required: true }) uploadLinkRelation: string; + @Input({ required: true }) filesResource: Resource; + @Input({ required: true }) filesLinkRelation: string; + @Input() listOrientation: BinaryFileListOrientation = BinaryFileListOrientation.HORIZONTAL; +} diff --git a/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.html b/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.html index b8b8d9e4c1d3addfff29d35af5e786923e194454..2649c1cdb731e0b5997df29d904241f569cf544d 100644 --- a/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.html +++ b/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.html @@ -23,28 +23,18 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<ozgcloud-spinner - *ngIf="binaryFileListStateResource.resource" - [stateResource]="binaryFileListStateResource" -> +<ozgcloud-spinner *ngIf="binaryFileListStateResource.resource" [stateResource]="binaryFileListStateResource"> <ods-attachment-wrapper data-test-id="file-list"> - <ods-attachment-header - [title]="title" - *ngIf="title || archiveDownloadUri" - data-test-id="file-list-header" - > + <ods-attachment-header [title]="title" *ngIf="title || archiveDownloadUri" data-test-id="file-list-header"> <alfa-download-archive-file-button-container *ngIf="archiveDownloadUri" - data-test-class="download-archive-file-button" + data-test-id="download-archive-file-button-container" [downloadUri]="archiveDownloadUri" action-buttons ></alfa-download-archive-file-button-container ></ods-attachment-header> <ng-container - *ngFor=" - let binaryFileResource of binaryFileListStateResource.resource - | toEmbeddedResources: fileListRel.FILE_LIST - " + *ngFor="let binaryFileResource of binaryFileListStateResource.resource | toEmbeddedResources: fileListRel.FILE_LIST" > <alfa-binary-file2-container [file]="binaryFileResource" diff --git a/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.spec.ts b/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.spec.ts index 93be7c71dab2c1f665423770e2c877a73e12c869..500c35cf858960e3a27674d2bf53ca9999555ca5 100644 --- a/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.spec.ts +++ b/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.spec.ts @@ -23,21 +23,14 @@ */ import { BinaryFileResource } from '@alfa-client/binary-file-shared'; import { ToEmbeddedResourcesPipe, createStateResource } from '@alfa-client/tech-shared'; -import { - existsAsHtmlElement, - getMockComponent, - notExistsAsHtmlElement, -} from '@alfa-client/test-utils'; +import { existsAsHtmlElement, getMockComponent, notExistsAsHtmlElement } from '@alfa-client/test-utils'; import { SpinnerComponent } from '@alfa-client/ui'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { faker } from '@faker-js/faker'; import { ResourceUri } from '@ngxp/rest'; import { AttachmentHeaderComponent, AttachmentWrapperComponent } from '@ods/system'; -import { - createBinaryFileListResource, - createBinaryFileResource, -} from 'libs/binary-file-shared/test/binary-file'; -import { getDataTestClassOf, getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { createBinaryFileListResource, createBinaryFileResource } from 'libs/binary-file-shared/test/binary-file'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; import { BinaryFile2ContainerComponent } from '../binary-file2-container/binary-file2-container.component'; import { DownloadArchiveFileButtonContainerComponent } from '../download-archive-file-button-container/download-archive-file-button-container.component'; @@ -47,7 +40,7 @@ describe('VerticalBinaryFileListComponent', () => { let component: VerticalBinaryFileListComponent; let fixture: ComponentFixture<VerticalBinaryFileListComponent>; - const downloadArchiveFileButton: string = getDataTestClassOf('download-archive-file-button'); + const downloadArchiveFileButton: string = getDataTestIdOf('download-archive-file-button-container'); const fileListHeader: string = getDataTestIdOf('file-list-header'); const binaryFile: BinaryFileResource = createBinaryFileResource(); @@ -69,9 +62,7 @@ describe('VerticalBinaryFileListComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(VerticalBinaryFileListComponent); component = fixture.componentInstance; - component.binaryFileListStateResource = createStateResource( - createBinaryFileListResource([binaryFile]), - ); + component.binaryFileListStateResource = createStateResource(createBinaryFileListResource([binaryFile])); fixture.detectChanges(); }); @@ -82,28 +73,19 @@ describe('VerticalBinaryFileListComponent', () => { describe('binary file component', () => { describe('should be called with', () => { it('file', () => { - const comp: BinaryFile2ContainerComponent = getMockComponent( - fixture, - BinaryFile2ContainerComponent, - ); + const comp: BinaryFile2ContainerComponent = getMockComponent(fixture, BinaryFile2ContainerComponent); expect(comp.file).toBe(binaryFile); }); it('deletable', () => { - const comp: BinaryFile2ContainerComponent = getMockComponent( - fixture, - BinaryFile2ContainerComponent, - ); + const comp: BinaryFile2ContainerComponent = getMockComponent(fixture, BinaryFile2ContainerComponent); expect(comp.deletable).toBe(component.deletable); }); it('downloadFileNamePrefix', () => { - const comp: BinaryFile2ContainerComponent = getMockComponent( - fixture, - BinaryFile2ContainerComponent, - ); + const comp: BinaryFile2ContainerComponent = getMockComponent(fixture, BinaryFile2ContainerComponent); expect(comp.downloadFileNamePrefix).toBe(component.downloadFileNamePrefix); }); diff --git a/alfa-client/libs/design-component/src/index.ts b/alfa-client/libs/design-component/src/index.ts index 745720be72b8665fb259372141e6ab5537efbfbe..9eec827c3a019115cc42c751521816fdde5c87a7 100644 --- a/alfa-client/libs/design-component/src/index.ts +++ b/alfa-client/libs/design-component/src/index.ts @@ -22,6 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ export * from './lib/button-with-spinner/button-with-spinner.component'; +export * from './lib/cancel-dialog-button/cancel-dialog-button.component'; export * from './lib/download-button/download-button.component'; export * from './lib/form/button-toggle-group/button-toggle-group.component'; export * from './lib/form/checkbox-editor/checkbox-editor.component'; @@ -30,5 +31,6 @@ export * from './lib/form/formcontrol-editor.abstract.component'; export * from './lib/form/single-file-upload-editor/single-file-upload-editor.component'; export * from './lib/form/text-editor/text-editor.component'; export * from './lib/form/textarea-editor/textarea-editor.component'; +export * from './lib/open-dialog-button/open-dialog-button.component'; export * from './lib/routing-button/routing-button.component'; export * from './lib/spinner/spinner.component'; diff --git a/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.spec.ts b/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..110c81e4d4072ef0417676ea9966a7fa3394f144 --- /dev/null +++ b/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.spec.ts @@ -0,0 +1,47 @@ +import { dispatchEventFromFixture, mock, Mock, MockEvent } from '@alfa-client/test-utils'; +import { OzgcloudDialogService } from '@alfa-client/ui'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ButtonComponent } from '@ods/system'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { CancelDialogButtonComponent } from './cancel-dialog-button.component'; + +describe('CancelDialogButtonComponent', () => { + let component: CancelDialogButtonComponent; + let fixture: ComponentFixture<CancelDialogButtonComponent>; + + let dialogService: Mock<OzgcloudDialogService>; + + const cancelDialog: string = getDataTestIdOf('cancel-dialog'); + + beforeEach(async () => { + dialogService = mock(OzgcloudDialogService); + + await TestBed.configureTestingModule({ + imports: [CancelDialogButtonComponent], + declarations: [MockComponent(ButtonComponent)], + providers: [ + { + provide: OzgcloudDialogService, + useValue: dialogService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(CancelDialogButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('on button click', () => { + it('should call dialog service to close all', () => { + dispatchEventFromFixture(fixture, cancelDialog, MockEvent.CLICK); + + expect(dialogService.closeAll).toHaveBeenCalled(); + }); + }); +}); diff --git a/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.ts b/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..5d80db0f4e7755c1a2bd9ae9eb7b921c3cf58349 --- /dev/null +++ b/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.ts @@ -0,0 +1,23 @@ +import { OzgcloudDialogService } from '@alfa-client/ui'; +import { Component, inject } from '@angular/core'; +import { ButtonComponent } from '@ods/system'; + +@Component({ + selector: 'ods-cancel-dialog-button', + standalone: true, + imports: [ButtonComponent], + template: `<ods-button + (clickEmitter)="cancel()" + variant="outline" + text="Abbrechen" + dataTestId="cancel-dialog" + data-test-id="cancel-dialog" + />`, +}) +export class CancelDialogButtonComponent { + public readonly dialogService = inject(OzgcloudDialogService); + + public cancel(): void { + this.dialogService.closeAll(); + } +} diff --git a/alfa-client/libs/design-component/src/lib/download-button/download-button.component.ts b/alfa-client/libs/design-component/src/lib/download-button/download-button.component.ts index 7c479ec1713ce6efca5777e04880575c623ba23d..14473d08c588ba1a2ee9d63a240cb30ca451d48b 100644 --- a/alfa-client/libs/design-component/src/lib/download-button/download-button.component.ts +++ b/alfa-client/libs/design-component/src/lib/download-button/download-button.component.ts @@ -46,7 +46,7 @@ type IconVariants = VariantProps<typeof iconVariants>; </ods-button>`, }) export class DownloadButtonComponent { - @Input() dataTestId: string = ''; + @Input({ required: true }) dataTestId: string; @Input() size: IconVariants['size'] = 'small'; @Input() set stateResource(resource: StateResource<CommandResource>) { this.isLoading = resource.loading || resource.reload; diff --git a/alfa-client/libs/design-component/src/lib/form/file-upload-editor/file-upload-editor.component.html b/alfa-client/libs/design-component/src/lib/form/file-upload-editor/file-upload-editor.component.html index ff08ce18e4ad8cc6037b84833bd941f375a6da0f..e3807c40af21c86b0026c201ed9cb12525aa263b 100644 --- a/alfa-client/libs/design-component/src/lib/form/file-upload-editor/file-upload-editor.component.html +++ b/alfa-client/libs/design-component/src/lib/form/file-upload-editor/file-upload-editor.component.html @@ -35,6 +35,7 @@ [attr.data-test-id]="(label | convertForDataTest) + '-file-upload-button'" [isLoading]="uploadInProgress.loading" class="relative w-full max-w-72" + dataTestId="file-upload-editor" > <ods-spinner-icon spinner size="medium" /> <ods-attachment-icon icon size="medium" /> diff --git a/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.html b/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.html index 837b069ad149e2e7e2eb08305e54e8ab74df16f5..409101e9b6320310d915e0364f8da49bf82a7fda 100644 --- a/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.html +++ b/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.html @@ -29,6 +29,7 @@ [isLoading]="uploadInProgress" [accept]="accept" [attr.data-test-id]="(label | convertForDataTest) + '-single-file-upload-button'" + dataTestId="single-file-upload-button" > <ng-content icon select="[icon]"></ng-content> <ng-content text select="[text]"></ng-content> diff --git a/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.ts index a62e384fb3c9ff9c15b453cc9927438d1e1790c4..7de4a0bf3fc04340fa80daed1896235559f6dd18 100644 --- a/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/single-file-upload-editor/single-file-upload-editor.component.ts @@ -24,7 +24,7 @@ import { ConvertForDataTestPipe, isNotNil } from '@alfa-client/tech-shared'; import { Component, EventEmitter, HostListener, Input, Output } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; -import { FileUploadButtonComponent, SpinnerIconComponent } from '@ods/system'; +import { FileUploadButtonComponent } from '@ods/system'; import { uniqueId } from 'lodash-es'; import { FormControlEditorAbstractComponent } from '../formcontrol-editor.abstract.component'; @@ -32,7 +32,8 @@ import { FormControlEditorAbstractComponent } from '../formcontrol-editor.abstra selector: 'ods-single-file-upload-editor', templateUrl: './single-file-upload-editor.component.html', standalone: true, - imports: [FileUploadButtonComponent, SpinnerIconComponent, ReactiveFormsModule, ConvertForDataTestPipe], + styles: [':host {@apply contents}'], + imports: [FileUploadButtonComponent, ReactiveFormsModule, ConvertForDataTestPipe], }) export class SingleFileUploadEditorComponent extends FormControlEditorAbstractComponent { @Input() label: string = ''; diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html index c4901be8608a9e1162963472472090a19fad3a5a..c4c009107436b4b7a8837c0fa9c31c4add1008d6 100644 --- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html +++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html @@ -29,7 +29,8 @@ [placeholder]="placeholder" [autocomplete]="autocomplete" [variant]="variant" - [attr.data-test-id]="(label | convertForDataTest) + '-text-editor'" + [attr.data-test-id]="dataTestId + '-text-editor'" + [dataTestId]="dataTestId" [required]="isRequired" [focus]="focus" [showLabel]="showLabel" @@ -38,6 +39,6 @@ error [invalidParams]="invalidParams" [label]="label" - [attr.data-test-id]="(label | convertForDataTest) + '-text-editor-error'" + [attr.data-test-id]="dataTestId + '-text-editor-error'" ></ods-validation-error> </ods-text-input> diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.spec.ts b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.spec.ts index 7774ab8ddcebff345d1d62853e8ced4e0773adae..e6dce44b7e467a5d99d3e45667f05c716e29fc68 100644 --- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.spec.ts +++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.spec.ts @@ -35,8 +35,9 @@ describe('TextEditorComponent', () => { let fixture: ComponentFixture<TextEditorComponent>; const labelText: string = faker.word.noun(); - const inputTestId: string = getDataTestIdOf(labelText + '-text-editor'); - const errorId: string = getDataTestIdOf(labelText + '-text-editor-error'); + const dataTestId: string = faker.string.alphanumeric(10); + const inputTestId: string = getDataTestIdOf(dataTestId + '-text-editor'); + const errorId: string = getDataTestIdOf(dataTestId + '-text-editor-error'); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -47,6 +48,7 @@ describe('TextEditorComponent', () => { fixture = TestBed.createComponent(TextEditorComponent); component = fixture.componentInstance; component.label = labelText; + component.dataTestId = dataTestId; fixture.detectChanges(); }); diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts index cfdd66512de073b74f0dbfd2bbed633573f9651f..45f7c42c8f3f609567e89d3075411ac652eb8882 100644 --- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts @@ -42,6 +42,7 @@ export class TextEditorComponent extends FormControlEditorAbstractComponent { @Input() isRequired: boolean = false; @Input() focus: boolean = false; @Input() showLabel: boolean = true; + @Input() dataTestId: string; get variant(): string { return this.invalidParams.length > 0 ? 'error' : 'default'; diff --git a/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.spec.ts b/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..edbd1a28e05784e5de0448629fa12c6beaa41669 --- /dev/null +++ b/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.spec.ts @@ -0,0 +1,103 @@ +import { OzgCloudComponentFactory } from '@alfa-client/tech-shared'; +import { dispatchEventFromFixture, Mock, mock, MockEvent, mockGetValue } from '@alfa-client/test-utils'; +import { DIALOG_COMPONENT, OzgcloudDialogService } from '@alfa-client/ui'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; +import { ComponentType } from '@angular/cdk/portal'; +import { ComponentRef, Injector, ViewContainerRef } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { expect } from '@jest/globals'; +import { ButtonComponent } from '@ods/system'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { of } from 'rxjs'; +import { OpenDialogButtonComponent } from './open-dialog-button.component'; + +describe('OpenDialogButtonComponent', () => { + let component: OpenDialogButtonComponent; + let fixture: ComponentFixture<OpenDialogButtonComponent>; + + let dialogComponent: Mock<ComponentType<any>>; + let componentFactory: Mock<OzgCloudComponentFactory>; + let dialogService: Mock<OzgcloudDialogService>; + let viewContainerRef: Mock<ViewContainerRef>; + let injector: Mock<Injector>; + + const openDialog: string = getDataTestIdOf('open-dialog'); + + const componentRef: ComponentRef<any> = <any>{ instance: { constructor: null } }; + const dialogResponse: any = {}; + const dummyDialogData: any = { someField: 'someValue' }; + + beforeEach(async () => { + dialogComponent = {}; + componentFactory = mock(OzgCloudComponentFactory); + dialogService = { ...mock(OzgcloudDialogService), openInContext: jest.fn().mockReturnValue({ closed: of(dialogResponse) }) }; + viewContainerRef = mock(ViewContainerRef as any); + injector = mock(Injector as any); + + await TestBed.configureTestingModule({ + imports: [OpenDialogButtonComponent], + declarations: [MockComponent(ButtonComponent)], + providers: [ + { + provide: DIALOG_COMPONENT, + useValue: dialogComponent, + }, + { + provide: OzgCloudComponentFactory, + useValue: componentFactory, + }, + { + provide: OzgcloudDialogService, + useValue: dialogService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(OpenDialogButtonComponent); + component = fixture.componentInstance; + mockGetValue(component, 'viewContainerRef', viewContainerRef); + mockGetValue(component, 'injector', injector); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('on button click', () => { + beforeEach(() => { + component._createComponent = jest.fn().mockReturnValue(componentRef); + }); + + it('should call create component factory to create component', () => { + dispatchEventFromFixture(fixture, openDialog, MockEvent.CLICK); + + expect(component._createComponent).toHaveBeenCalled(); + }); + + it('should call dialog service to open dialog', () => { + component.dialogData = dummyDialogData; + + dispatchEventFromFixture(fixture, openDialog, MockEvent.CLICK); + + expect(dialogService.openInContext).toHaveBeenCalledWith( + componentRef.instance.constructor, + viewContainerRef, + dummyDialogData, + ); + }); + }); + + describe('create component', () => { + it('should call component factory to create component', () => { + component.dialogData = dummyDialogData; + + component._createComponent(); + + expect(componentFactory.createComponent).toHaveBeenCalledWith(dialogComponent, injector, [ + { provide: DIALOG_DATA, useValue: dummyDialogData }, + ]); + }); + }); +}); diff --git a/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.ts b/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..974cffb092526ebbc9bf0839b057ea0dde1297ec --- /dev/null +++ b/alfa-client/libs/design-component/src/lib/open-dialog-button/open-dialog-button.component.ts @@ -0,0 +1,51 @@ +import { OzgCloudComponentFactory } from '@alfa-client/tech-shared'; +import { DIALOG_COMPONENT, OzgcloudDialogService } from '@alfa-client/ui'; +import { DIALOG_DATA } from '@angular/cdk/dialog'; +import { ComponentType } from '@angular/cdk/portal'; +import { Component, ComponentRef, inject, Injector, Input, ViewContainerRef } from '@angular/core'; +import { ButtonComponent, ButtonVariants } from '@ods/system'; + +@Component({ + selector: 'ods-open-dialog-button', + standalone: true, + imports: [ButtonComponent], + template: ` <ods-button + (clickEmitter)="open()" + [variant]="variant" + [text]="label" + [size]="size" + [dataTestId]="dataTestId" + [dataTestClass]="dataTestClass" + data-test-id="open-dialog" + > + <ng-container icon> + <ng-content select="[icon]" /> + </ng-container> + </ods-button>`, +}) +export class OpenDialogButtonComponent { + private readonly component: ComponentType<any> = inject(DIALOG_COMPONENT); + private readonly componentFactory = inject(OzgCloudComponentFactory); + + private readonly dialogService = inject(OzgcloudDialogService); + + readonly viewContainerRef = inject(ViewContainerRef); + private readonly injector = inject(Injector); + + @Input() label: string; + @Input() dataTestId: string; + @Input() dataTestClass: string; + @Input() variant: ButtonVariants['variant'] = 'primary'; + @Input() dialogData: any; + @Input() size: ButtonVariants['size']; + + public open(): void { + this.dialogService.openInContext(this._createComponent().instance.constructor, this.viewContainerRef, this.dialogData); + } + + _createComponent(): ComponentRef<any> { + return this.componentFactory.createComponent<any>(this.component, this.injector, [ + { provide: DIALOG_DATA, useValue: this.dialogData }, + ]); + } +} diff --git a/alfa-client/libs/design-system/src/lib/button/button.component.ts b/alfa-client/libs/design-system/src/lib/button/button.component.ts index 15a9b7efdc261b462f3dade0f93b2a6902e36507..47e76172adb5e6f9600be54291ee79a841defb78 100644 --- a/alfa-client/libs/design-system/src/lib/button/button.component.ts +++ b/alfa-client/libs/design-system/src/lib/button/button.component.ts @@ -30,7 +30,7 @@ import { SpinnerIconComponent } from '../icons/spinner-icon/spinner-icon.compone export const buttonVariants = cva( [ - 'flex items-center gap-4 rounded-lg text-sm font-medium box-border', + 'flex items-center gap-3 rounded-lg text-sm font-medium box-border', 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2', ], { @@ -39,6 +39,8 @@ export const buttonVariants = cva( primary: 'bg-primary text-whitetext shadow-md hover:enabled:bg-primary-hover focus-visible:bg-primary-hover', outline: 'border border-primary bg-background-50 text-primary shadow-md hover:enabled:bg-ghost-hover focus-visible:bg-ghost-hover focus-visible:border-background-200', + outline_error: + 'border border-error bg-background-50 text-error shadow-md hover:enabled:bg-ghost-hover focus-visible:bg-ghost-hover focus-visible:border-background-200', ghost: 'border border-transparent hover:enabled:bg-ghost-hover text-primary focus-visible:border-background-200 focus-visible:bg-ghost-hover font-semibold [&]:focus-visible:outline-offset-1', }, @@ -87,23 +89,25 @@ export type ButtonVariants = VariantProps<typeof buttonVariants>; selector: 'ods-button', standalone: true, imports: [CommonModule, SpinnerIconComponent], - template: `<button + template: ` <button type="button" [ngClass]="buttonVariants({ size, variant, disabled, destructive })" [disabled]="isDisabled" [attr.aria-disabled]="isDisabled" [attr.aria-label]="text" [attr.data-test-id]="dataTestId" + [attr.data-test-class]="dataTestClass" (click)="clickEmitter.emit()" > <ng-content *ngIf="!isLoading" select="[icon]"></ng-content> - <ods-spinner-icon *ngIf="isLoading" [size]="spinnerSize"></ods-spinner-icon> + <ods-spinner-icon *ngIf="isLoading" [size]="spinnerSize" data-test-class="spinner"></ods-spinner-icon> <div *ngIf="text" class="flex-grow">{{ text }}</div> </button>`, }) export class ButtonComponent { @Input() text: string = ''; @Input() dataTestId: string = ''; + @Input() dataTestClass: string = ''; @Input() disabled: boolean = false; @Input() isLoading: boolean = false; @Input({ transform: booleanAttribute }) destructive: boolean = false; diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.spec.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.spec.ts index 0b747f1e0507e5375a8dd0baba6a7ce2733ba89c..1af5235f276bf8afd8787fd155e104f3a8b64040 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.spec.ts @@ -43,13 +43,13 @@ describe('DropdownMenuButtonItemComponent', () => { expect(component).toBeTruthy(); }); - describe('itemClicked emitter', () => { - it('should emit itemClicked', () => { - component.itemClicked.emit = jest.fn(); + describe('clickEmitter', () => { + it('should emit', () => { + component.clickEmitter.emit = jest.fn(); dispatchEventFromFixture(fixture, 'button', 'click'); - expect(component.itemClicked.emit).toHaveBeenCalled(); + expect(component.clickEmitter.emit).toHaveBeenCalled(); }); }); }); diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.ts index 2c1b5427927141d6b7ca40515ee64432d9d50e2e..85f7e1f0f6895e6798d6697f3e7e63391be8ca14 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component.ts @@ -28,19 +28,21 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; selector: 'ods-dropdown-menu-button-item', standalone: true, imports: [CommonModule], - template: `<button - class="flex min-h-12 w-full items-center gap-4 border-2 border-transparent px-4 py-3 text-start outline-none hover:border-primary focus-visible:border-focus" - role="menuitem" - (click)="itemClicked.emit()" - [attr.data-test-id]="dataTestId" - > - <ng-content select="[icon]" /> - <p class="text-text">{{ caption }}</p> - </button>`, + template: ` <div class="w-full bg-whitetext p-1.5"> + <button + class="flex w-full items-center gap-2 rounded-md border border-transparent px-4 py-2 text-start font-medium outline-none hover:bg-background-150 focus-visible:border-primary dark:hover:bg-neutral-700" + role="menuitem" + (click)="clickEmitter.emit()" + [attr.data-test-id]="dataTestId" + > + <ng-content select="[icon]" /> + <p class="text-sm text-primary">{{ caption }}</p> + </button> + </div>`, }) export class DropdownMenuButtonItemComponent { @Input({ required: true }) caption!: string; @Input() dataTestId: string; - @Output() itemClicked: EventEmitter<MouseEvent> = new EventEmitter(); + @Output() clickEmitter: EventEmitter<MouseEvent> = new EventEmitter(); } diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component.ts index 301af6719e17a63de18c5141a3bec564265976cd..2314cab202a68e6d276c7d0a3a731ae15df5c239 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component.ts @@ -7,6 +7,6 @@ import { Component } from '@angular/core'; imports: [CommonModule], styles: [':host {@apply block min-h-12 px-4 py-3 first:mt-2 last:mb-2}'], - template: ` <ng-content /> `, + template: `<ng-content />`, }) export class DropdownMenuItemComponent {} diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component.ts index 045191b56a76618a85600a82757607f267b4c8e4..24427bb02e4ed24c83d01a0c4c942b474ef7385a 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from '@angular/common'; import { Component, Input } from '@angular/core'; import { OpenLinkIconComponent } from '../../icons/open-link-icon/open-link-icon.component'; import { LinkComponent } from '../../link/link.component'; @@ -5,17 +6,27 @@ import { LinkComponent } from '../../link/link.component'; @Component({ selector: 'ods-dropdown-menu-link-item', standalone: true, - imports: [OpenLinkIconComponent, LinkComponent], - styles: [':host {@apply first:mt-2}'], - template: ` <ods-link [url]="url" class="bg-whitetext" [openInNewTab]="true"> - <div class="flex items-center gap-2 px-4 py-3"> - <p class="font-medium text-primary">{{ text }}</p> - <ods-open-link-icon class="size-5" /> - <span class="sr-only">Öffnet in einem neuen Tab</span> - </div> - </ods-link>`, + imports: [LinkComponent, OpenLinkIconComponent, CommonModule], + template: `<div class="w-full bg-whitetext p-1.5"> + <ods-link [url]="url" [openInNewTab]="true"> + <div class="flex min-w-80 gap-3 px-3 py-1.5"> + <ng-content select="[icon]" /> + <div class="flex flex-col gap-1"> + @if (caption) { + <p class="text-sm font-medium text-text">{{ caption }}</p> + } + <div class="flex items-center gap-2"> + <p class="text-sm font-normal text-primary">{{ text }}</p> + <ods-open-link-icon size="small" /> + <span class="sr-only">Öffnet in einem neuen Tab</span> + </div> + </div> + </div> + </ods-link> + </div>`, }) export class DropdownMenuLinkItemComponent { @Input() url: string; + @Input() caption: string; @Input() text: string; } diff --git a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu/dropdown-menu.component.ts b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu/dropdown-menu.component.ts index 04a15c7eec0780ac9b8709224ab54aecaf77ab6f..24c78fceb054182a65572cef755fa379dddca255 100644 --- a/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu/dropdown-menu.component.ts +++ b/alfa-client/libs/design-system/src/lib/dropdown-menu/dropdown-menu/dropdown-menu.component.ts @@ -46,9 +46,10 @@ import { twMerge } from 'tailwind-merge'; > <ng-content select="[button-content]" /> </button> + <div *ngIf="isPopupOpen" - class="absolute z-50 max-h-120 min-w-44 max-w-96 animate-fadeIn overflow-y-auto rounded bg-dropdownBg shadow-md ring-1 ring-grayborder focus:outline-none" + class="absolute z-50 mt-2 min-w-44 max-w-96 animate-fadeIn rounded-lg bg-background-100 ring-1 ring-grayborder drop-shadow-lg focus:outline-none" [ngClass]="alignTo === 'left' ? 'right-0' : 'left-0'" role="menu" aria-modal="true" diff --git a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.html b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.html index ece1c85988571d733845ce9403521c3b804d0c23..2e4573ac9c648f820f392f51dc8e96c237f74fb5 100644 --- a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.html +++ b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.html @@ -31,16 +31,16 @@ [accept]="accept" (click)="resetInput()" [disabled]="isLoading" - [attr.data-test-id]="(id | convertForDataTest) + '-file-upload-input'" + [multiple]="multi" + [attr.data-test-id]="dataTestId" /> -<label - [for]="id" - class="z-10 inline-flex w-full flex-grow items-center justify-start gap-4 break-words rounded-md bg-background-50 py-3 pl-6 pr-6 text-text hover:bg-background-100 focus:outline-none focus:ring-2 focus:ring-primary peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ozgblue-800 peer-disabled:cursor-wait peer-disabled:hover:bg-background-50" - role="button" -> - <ng-content *ngIf="!isLoading" select="[icon]"></ng-content> - <ng-content *ngIf="isLoading" select="[spinner]"></ng-content> - <div class="flex-grow"> +<label [for]="id" [ngClass]="uploadButtonVariants({ variant })" role="button"> + @if (isLoading) { + <ng-content select="[spinner]"></ng-content> + } @else { + <ng-content select="[icon]"></ng-content> + } + <div class="flex-grow empty:hidden"> <ng-content select="[text]"></ng-content> </div> </label> diff --git a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.spec.ts b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.spec.ts index fee4fa492f168106fce4c7f8c97dc6b31a956ddd..bc63bfe1e214deec130fcd1f487045d14a68ad79 100644 --- a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.spec.ts @@ -21,10 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; import { getElementFromFixture } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { faker } from '@faker-js/faker'; +import { expect } from '@jest/globals'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { FileUploadButtonComponent } from './file-upload-button.component'; @@ -33,16 +33,18 @@ describe('FileUploadButtonComponent', () => { let fixture: ComponentFixture<FileUploadButtonComponent>; const labelText: string = faker.word.noun(); - const inputTestClass: string = getDataTestIdOf(labelText + '-file-upload-input'); + const dataTestId: string = 'dummyDataTestId'; + const input: string = getDataTestIdOf(dataTestId); beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FileUploadButtonComponent, ConvertForDataTestPipe], + imports: [FileUploadButtonComponent], }).compileComponents(); fixture = TestBed.createComponent(FileUploadButtonComponent); component = fixture.componentInstance; component.id = labelText; + component.dataTestId = dataTestId; fixture.detectChanges(); }); @@ -53,7 +55,7 @@ describe('FileUploadButtonComponent', () => { describe('click at file input', () => { it('should call resetInput()', () => { component.resetInput = jest.fn(); - const fileInput = getElementFromFixture(fixture, inputTestClass); + const fileInput = getElementFromFixture(fixture, input); fileInput.click(); fixture.detectChanges(); @@ -61,4 +63,15 @@ describe('FileUploadButtonComponent', () => { expect(component.resetInput).toHaveBeenCalled(); }); }); + + describe('template', () => { + it('should have inputs', () => { + component.multi = true; + fixture.detectChanges(); + + const inputElement: HTMLInputElement = getElementFromFixture(fixture, input); + + expect(inputElement.multiple).toEqual(component.multi); + }); + }); }); diff --git a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.ts b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.ts index 3e111b19ad208b4b4505a67bb8ef343ae6de0d2e..9f06d3864e0c7767b368509d49449e68d18ac0a0 100644 --- a/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.ts +++ b/alfa-client/libs/design-system/src/lib/form/file-upload-button/file-upload-button.component.ts @@ -24,21 +24,47 @@ import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; import { Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { cva, VariantProps } from 'class-variance-authority'; + +export const uploadButtonVariants = cva( + [ + 'z-10 inline-flex flex-grow items-center justify-start gap-4 break-words rounded-md text-primary', + 'border border-transparent hover:bg-ghost-hover peer-focus-visible:border-background-200', + 'peer-focus-visible:outline peer-focus-visible:outline-focus peer-focus-visible:bg-ghost-hover peer-focus-visible:outline-offset-1', + ], + { + variants: { + variant: { + label: 'py-3 px-6 bg-background-50 w-full', + icon: 'p-2 w-fit', + }, + }, + defaultVariants: { + variant: 'label', + }, + }, +); +export type UploadButtonVariants = VariantProps<typeof uploadButtonVariants>; @Component({ selector: 'ods-file-upload-button', standalone: true, imports: [CommonModule, ConvertForDataTestPipe], - styles: [':host {@apply inline-flex}'], + styles: [':host {@apply relative}'], templateUrl: './file-upload-button.component.html', }) export class FileUploadButtonComponent { @Input({ required: true }) id!: string; @Input() isLoading: boolean = false; @Input() accept: string = '*/*'; + @Input() multi: boolean = false; + @Input() variant: UploadButtonVariants['variant']; + @Input({ required: true }) dataTestId: string; @ViewChild('inputElement') inputElement: ElementRef = new ElementRef({}); + readonly uploadButtonVariants = uploadButtonVariants; + resetInput(): void { this.inputElement.nativeElement.value = ''; } diff --git a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts index ca6a007c19e49ebb481ff0655b407cc3076a9c08..1b8bf5fdf47948bbc9374b01086238c3e41c0ac3 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 @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { convertForDataTest, ConvertForDataTestPipe, EMPTY_STRING } from '@alfa-client/tech-shared'; +import { convertForDataTest, ConvertForDataTestPipe, EMPTY_STRING, isNotUndefined } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; @@ -70,7 +70,7 @@ type TextInputVariants = VariantProps<typeof textInputVariants>; [autocomplete]="autocomplete" [attr.aria-required]="required" [attr.aria-invalid]="variant === 'error'" - [attr.data-test-id]="(inputLabel | convertForDataTest) + '-text-input'" + [attr.data-test-id]="_dataTestId + '-text-input'" (click)="clickEmitter.emit()" #inputElement /> @@ -88,6 +88,7 @@ export class TextInputComponent { @Input({ required: true }) set label(label: string) { this.inputLabel = label; this.id = `${convertForDataTest(label)}-${uniqueId()}`; + this._dataTestId = convertForDataTest(this.inputLabel); } @Input() placeholder: string = ''; @Input() autocomplete: string = 'off'; @@ -97,6 +98,9 @@ export class TextInputComponent { @Input() withPrefix: boolean = false; @Input() withSuffix: boolean = false; @Input() showLabel: boolean = true; + @Input() set dataTestId(value: string) { + if (isNotUndefined(value)) this._dataTestId = value; + } @Input() set focus(value: boolean) { if (value && this.inputElement) { @@ -108,5 +112,6 @@ export class TextInputComponent { inputLabel: string; id: string; + _dataTestId: string; textInputVariants = textInputVariants; } diff --git a/alfa-client/libs/design-system/src/lib/icons/file-icon/file-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/file-icon/file-icon.component.ts index 2c084618d209a629d826c35f1397e8ff6c1de6d0..1e3a7ec67b3136e428a177080bd99f7410cb6ae1 100644 --- a/alfa-client/libs/design-system/src/lib/icons/file-icon/file-icon.component.ts +++ b/alfa-client/libs/design-system/src/lib/icons/file-icon/file-icon.component.ts @@ -38,6 +38,7 @@ const fileiconVariants = cva('fill-ozggray-300', { }, size: { small: 'w-4 h-5', + medium: 'w-5 h-6', large: 'w-8 h-10', }, }, diff --git a/alfa-client/libs/design-system/src/lib/icons/spinner-icon/spinner-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/spinner-icon/spinner-icon.component.ts index 503f6865258a83c1ce2073c288e47f68b87e99ef..d361292b08e026c2f05e245276dae53b82fa2805 100644 --- a/alfa-client/libs/design-system/src/lib/icons/spinner-icon/spinner-icon.component.ts +++ b/alfa-client/libs/design-system/src/lib/icons/spinner-icon/spinner-icon.component.ts @@ -30,13 +30,15 @@ import { IconVariants, iconVariants } from '../iconVariants'; selector: 'ods-spinner-icon', standalone: true, imports: [NgClass], - template: `<svg + template: ` + <svg xmlns="http://www.w3.org/2000/svg" [ngClass]="iconVariants({ size })" class="animate-spin fill-primary text-gray-200 dark:text-gray-600" aria-hidden="true" viewBox="0 0 100 100" fill="none" + data-test-class="spinner" > <path d="M100 50.59c0 27.615-22.386 50.001-50 50.001s-50-22.386-50-50 22.386-50 50-50 50 22.386 50 50Zm-90.919 0c0 22.6 18.32 40.92 40.919 40.92 22.599 0 40.919-18.32 40.919-40.92 0-22.598-18.32-40.918-40.919-40.918-22.599 0-40.919 18.32-40.919 40.919Z" @@ -46,7 +48,8 @@ import { IconVariants, iconVariants } from '../iconVariants'; d="M93.968 39.04c2.425-.636 3.894-3.128 3.04-5.486A50 50 0 0 0 41.735 1.279c-2.474.414-3.922 2.919-3.285 5.344.637 2.426 3.12 3.849 5.6 3.484a40.916 40.916 0 0 1 44.131 25.769c.902 2.34 3.361 3.802 5.787 3.165Z" /> </svg> - <span class="sr-only">Loading...</span> `, + <span class="sr-only">Loading...</span> + `, }) export class SpinnerIconComponent { @Input() size: IconVariants['size'] = 'full'; diff --git a/alfa-client/libs/design-system/src/lib/link/link.component.ts b/alfa-client/libs/design-system/src/lib/link/link.component.ts index 64830deee7cf6873156013281ec33d7e2a56199e..a7f529ad5ca57b02e72dcb6033b128a9c2e77416 100644 --- a/alfa-client/libs/design-system/src/lib/link/link.component.ts +++ b/alfa-client/libs/design-system/src/lib/link/link.component.ts @@ -11,7 +11,7 @@ import { twMerge } from 'tailwind-merge'; [href]="url" [class]=" twMerge( - 'block rounded-lg border-2 border-transparent text-text hover:bg-ghost-hover focus-visible:border-focus focus-visible:bg-ghost-hover focus-visible:outline-none dark:hover:bg-neutral-700', + 'block rounded-md border border-transparent text-text hover:bg-neutral-100 focus-visible:border-primary focus-visible:outline-none dark:hover:bg-neutral-700', class ) " diff --git a/alfa-client/libs/design-system/src/lib/list/list-item/list-item.component.spec.ts b/alfa-client/libs/design-system/src/lib/list/list-item/list-item.component.spec.ts index 7bfdff811e56e7986b589f06cad84a596b4ddf59..fff8eddf2051dbd1cbf53c80e1f5c733cae7e42a 100644 --- a/alfa-client/libs/design-system/src/lib/list/list-item/list-item.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/list/list-item/list-item.component.spec.ts @@ -24,7 +24,7 @@ import { getElementFromFixture } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { provideRouter } from '@angular/router'; -import { faker } from '@faker-js/faker'; +import { faker } from '@faker-js/faker/.'; import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; import { ListItemComponent } from './list-item.component'; @@ -32,6 +32,8 @@ describe('ListItemComponent', () => { let component: ListItemComponent; let fixture: ComponentFixture<ListItemComponent>; + const link: string = getDataTestClassOf('list-item-link'); + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ListItemComponent], @@ -50,13 +52,12 @@ describe('ListItemComponent', () => { describe('input', () => { describe('routerLink', () => { it('should set href attribute', () => { - component.path = encodeURI(faker.system.filePath()); - const resultingLink: string = 'http://localhost' + component.path; - const linkElement: HTMLLinkElement = getElementFromFixture(fixture, getDataTestClassOf('list-item-link')); + component.path = encodeURIComponent(faker.system.filePath()); fixture.detectChanges(); - expect(linkElement.href).toBe(resultingLink); + const linkElement: HTMLLinkElement = getElementFromFixture(fixture, link); + expect(decodeURIComponent(linkElement.href)).toBe(`http://localhost/${component.path}`); }); }); }); diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts index 671af957965555f680862dad37081cc00c537c9d..f62b0e92488cc33d05bbc61112556850a74766e9 100644 --- a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts @@ -29,6 +29,7 @@ import { TooltipPosition } from './tooltip.directive'; selector: 'ods-tooltip', imports: [NgClass], template: `<span + [attr.data-test-id]="dataTestId" class="tooltip fixed z-[100] animate-fadeIn cursor-default break-words rounded bg-ozggray-900 px-3 py-2 text-sm font-normal text-whitetext before:absolute before:border-l-[0.5rem] before:border-r-[0.5rem] before:border-l-transparent before:border-r-transparent dark:bg-white" [ngClass]="class" [class.visible]="show" @@ -58,6 +59,8 @@ export class TooltipComponent { class: string; leftOffset: number; + dataTestId: string; + set position(value: TooltipPosition) { if (value === TooltipPosition.ABOVE) { this.class = diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts index 76c569e4245841930a5387dc5c8a2e34e6328c1b..3b775dd56d43e4247a3de4cf06fe0a3e4d64bc76 100644 --- a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts @@ -21,6 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { assignValue, mock, useFromMock } from '@alfa-client/test-utils'; import { InteractivityChecker } from '@angular/cdk/a11y'; import { ComponentRef, ElementRef, Renderer2, ViewContainerRef } from '@angular/core'; import { fakeAsync, TestBed, tick } from '@angular/core/testing'; @@ -37,6 +38,18 @@ class MockElementRef extends ElementRef { describe('TooltipDirective', () => { let directive: TooltipDirective; + const mockComponentRefInstance: TooltipComponent = { + id: '', + left: 0, + top: 0, + text: '', + show: false, + position: TooltipPosition.BELOW, + class: '', + leftOffset: 0, + width: null, + dataTestId: undefined, + }; const mockComponentRef: ComponentRef<TooltipComponent> = { setInput: jest.fn(), destroy: jest.fn(), @@ -46,17 +59,7 @@ describe('TooltipDirective', () => { location: null, hostView: null, injector: null, - instance: { - id: '', - left: 0, - top: 0, - text: '', - show: false, - position: TooltipPosition.BELOW, - class: '', - leftOffset: 0, - width: null, - }, + instance: mockComponentRefInstance, }; const parentRect: DOMRect = { bottom: 0, top: 0, height: 0, left: 0, right: 0, toJSON: jest.fn(), width: 0, x: 0, y: 0 }; @@ -119,6 +122,7 @@ describe('TooltipDirective', () => { directive.setInitialTooltipProperties = jest.fn(); directive.getParentElement = jest.fn().mockReturnValue({ appendChild: jest.fn() }); directive.interactivityChecker.isFocusable = jest.fn(); + directive._addDataTestId = jest.fn(); }); it('should create tooltip component', () => { @@ -156,6 +160,43 @@ describe('TooltipDirective', () => { expect(directive.getParentElement).not.toHaveBeenCalled(); }); + + it('should call add data test id', () => { + directive.createTooltip(tooltipText); + + expect(directive._addDataTestId).toHaveBeenCalled(); + }); + }); + + describe('add data test id', () => { + const dataTestId: string = 'dummyDataTestId'; + + beforeEach(() => { + directive.componentRef = assignValue(mockComponentRef, 'instance', { ...mockComponentRef.instance, dataTestId: undefined }); + }); + + it('should set attribute to tooltip component if exists', () => { + directive.parentElement = mockParentAttribute(dataTestId); + + directive._addDataTestId(); + + expect(directive.componentRef.instance.dataTestId).toBe(dataTestId + directive.dataTestIdTooltipSuffix); + }); + + it('should NOT set attribute to tooltip component if undefined', () => { + directive.parentElement = mockParentAttribute(undefined); + + directive._addDataTestId(); + + expect(directive.componentRef.instance.dataTestId).toBeUndefined(); + }); + + function mockParentAttribute(value: any): HTMLElement { + return useFromMock<HTMLElement>({ + ...mock(HTMLElement), + getAttribute: jest.fn().mockReturnValue(value), + }); + } }); describe('showTooltip', () => { diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts index d8f42d4ab8dd4e8bdab6e49031a00fbbbddbb480..10cca117f0ab929c71c43dc0a33482a57a5c19de 100644 --- a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { isEscapeKey, isNotNull } from '@alfa-client/tech-shared'; +import { isEscapeKey, isNotNull, isNotUndefined } from '@alfa-client/tech-shared'; import { InteractivityChecker } from '@angular/cdk/a11y'; import { ComponentRef, @@ -80,6 +80,10 @@ export class TooltipDirective implements OnDestroy { public readonly renderer: Renderer2 = inject(Renderer2); public readonly interactivityChecker: InteractivityChecker = inject(InteractivityChecker); + dataTestIdParentAttribute: string = 'data-test-id'; + dataTestIdAttribute: string = 'dataTestId'; + dataTestIdTooltipSuffix: string = '-tooltip'; + ngOnDestroy(): void { this.destroy(); } @@ -120,12 +124,18 @@ export class TooltipDirective implements OnDestroy { const nativeElement: HTMLElement = this.elementRef.nativeElement; this.componentRef = this.viewContainerRef.createComponent(TooltipComponent); this.parentElement = this.getParentElement(nativeElement); + this._addDataTestId(); this.parentElement.appendChild(this.componentRef.location.nativeElement); this.tooltipId = uniqueId('tooltip'); this.setInitialTooltipProperties(tooltipText, this.tooltipId); this.setAriaAttribute(this.tooltipAriaType); } + _addDataTestId(): void { + const dataTestId: string = this.parentElement.getAttribute(this.dataTestIdParentAttribute); + if (isNotUndefined(dataTestId)) this.componentRef.instance.dataTestId = dataTestId + this.dataTestIdTooltipSuffix; + } + setInitialTooltipProperties(text: string, id: string) { this.componentRef.instance.text = text; this.componentRef.instance.id = id; diff --git a/alfa-client/libs/historie/src/lib/historie.module.ts b/alfa-client/libs/historie/src/lib/historie.module.ts index c9675a36427c958658176eac4776cbc1fdf23c9d..e0b620b84ae95157372eb714c2dba636e8fe448f 100644 --- a/alfa-client/libs/historie/src/lib/historie.module.ts +++ b/alfa-client/libs/historie/src/lib/historie.module.ts @@ -34,7 +34,7 @@ import { AccordionComponent, AppIconComponent, SpinnerComponent } from '@alfa-cl import { UserProfileModule } from '@alfa-client/user-profile'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { MatExpansionPanel, MatExpansionPanelTitle } from '@angular/material/expansion'; +import { MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle } from '@angular/material/expansion'; import { MatIcon } from '@angular/material/icon'; import { FormatFullDatePipe } from '../../../tech-shared/src/lib/pipe/format-full-date.pipe'; import { HistorieContainerComponent } from './historie-container/historie-container.component'; @@ -76,6 +76,7 @@ import { HistorieListComponent } from './historie-container/historie-list/histor SpinnerComponent, AccordionComponent, ToEmbeddedResourcesPipe, + MatExpansionPanelHeader, ], declarations: [ HistorieContainerComponent, diff --git a/alfa-client/libs/kommentar-shared/src/lib/kommentar.model.ts b/alfa-client/libs/kommentar-shared/src/lib/kommentar.model.ts index f6109a2ea77cff02a183eddaa6a4178244c68e93..2a6aa6711ee3df877983926e9d2d8add9c5fe663 100644 --- a/alfa-client/libs/kommentar-shared/src/lib/kommentar.model.ts +++ b/alfa-client/libs/kommentar-shared/src/lib/kommentar.model.ts @@ -32,4 +32,7 @@ export interface Kommentar { } export interface KommentarResource extends Kommentar, Resource {} + export interface KommentarListResource extends ListResource {} + +export const KOMMENTAR_UPLOADED_ATTACHMENTS: string = 'kommentar_uploaded_attachments'; diff --git a/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.spec.ts b/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.spec.ts index 329ac2f142356ce68c2d841605541322ac458dbb..522ed1f6ce6a4ae086d1226253f1d1236eb3eb9d 100644 --- a/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.spec.ts +++ b/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.spec.ts @@ -22,32 +22,23 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BinaryFileService } from '@alfa-client/binary-file-shared'; -import { - CommandOrder, - CommandResource, - CommandService, - CreateCommand, -} from '@alfa-client/command-shared'; +import { CommandOrder, CommandResource, CommandService, CreateCommand } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { - StateResource, - createEmptyStateResource, - createStateResource, -} from '@alfa-client/tech-shared'; +import { createEmptyStateResource, createStateResource, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { VorgangService, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; +import { faker } from '@faker-js/faker'; +import { expect } from '@jest/globals'; +import { ResourceUri } from '@ngxp/rest'; import { cold, hot } from 'jest-marbles'; import { CommandLinkRel } from 'libs/command-shared/src/lib/command.linkrel'; import { createCommandResource } from 'libs/command-shared/test/command'; -import { - createKommentar, - createKommentarListResource, - createKommentarResource, -} from 'libs/kommentar-shared/test/kommentar'; +import { createKommentar, createKommentarListResource, createKommentarResource } from 'libs/kommentar-shared/test/kommentar'; import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; import { of } from 'rxjs'; +import { singleCold } from '../../../tech-shared/test/marbles'; import { KommentarLinkRel, KommentarListLinkRel } from './kommentar.linkrel'; -import { Kommentar, KommentarListResource, KommentarResource } from './kommentar.model'; +import { Kommentar, KOMMENTAR_UPLOADED_ATTACHMENTS, KommentarListResource, KommentarResource } from './kommentar.model'; import { KommentarRepository } from './kommentar.repository'; import { KommentarService } from './kommentar.service'; @@ -120,9 +111,7 @@ describe('KommentarService', () => { it('should be return', () => { const result = service.createKommentar(kommentar); - expect(result).toBeObservable( - cold('ab', { a: createEmptyStateResource(true), b: commandStateResource }), - ); + expect(result).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: commandStateResource })); }); }); @@ -174,9 +163,7 @@ describe('KommentarService', () => { it('should be return', () => { const result = service.editKommentar(kommentarResource, kommentar); - expect(result).toBeObservable( - cold('ab', { a: createEmptyStateResource(true), b: commandStateResource }), - ); + expect(result).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: commandStateResource })); }); }); @@ -272,6 +259,23 @@ describe('KommentarService', () => { }); describe('onNavigation', () => { + beforeEach(() => { + service.clearUploadedFiles = jest.fn(); + service.hideFormular = jest.fn(); + }); + + it('should clear uploaded files', () => { + service.onNavigation({}); + + expect(service.clearUploadedFiles).toHaveBeenCalled(); + }); + + it('should hide forumlar', () => { + service.onNavigation({}); + + expect(service.hideFormular).toHaveBeenCalled(); + }); + describe('to vorgang list', () => { it('should set reload flag of kommentar list', () => { service.kommentarList$.next(createEmptyStateResource()); @@ -298,10 +302,7 @@ describe('KommentarService', () => { const kommentarResource = createKommentarResource([KommentarLinkRel.ATTACHMENTS]); service.getAttachments(kommentarResource); - expect(binaryFileService.getFiles).toHaveBeenCalledWith( - kommentarResource, - KommentarLinkRel.ATTACHMENTS, - ); + expect(binaryFileService.getFiles).toHaveBeenCalledWith(kommentarResource, KommentarLinkRel.ATTACHMENTS); }); it('should not be loaded if no link available', () => { @@ -315,9 +316,7 @@ describe('KommentarService', () => { it('should create new Kommentare', () => { const canCreateNewKommentar$ = cold('a', { a: true }); - const observable = service.canCreateNewKommentar( - createKommentarListResource([KommentarListLinkRel.CREATE_KOMMENTAR]), - ); + const observable = service.canCreateNewKommentar(createKommentarListResource([KommentarListLinkRel.CREATE_KOMMENTAR])); expect(observable).toBeObservable(canCreateNewKommentar$); }); @@ -326,9 +325,7 @@ describe('KommentarService', () => { const canCreateNewKommentar$ = cold('a', { a: false }); service.formularVisibility$.next(true); - const observable = service.canCreateNewKommentar( - createKommentarListResource([KommentarListLinkRel.CREATE_KOMMENTAR]), - ); + const observable = service.canCreateNewKommentar(createKommentarListResource([KommentarListLinkRel.CREATE_KOMMENTAR])); expect(observable).toBeObservable(canCreateNewKommentar$); }); @@ -342,4 +339,86 @@ describe('KommentarService', () => { expect(observable).toBeObservable(canCreateNewKommentar$); }); }); + + describe('clearUploadedFiles', () => { + it('should call binary file service', () => { + service.clearUploadedFiles(); + + expect(binaryFileService.clearUploadedFiles).toHaveBeenCalledWith(KOMMENTAR_UPLOADED_ATTACHMENTS); + }); + }); + + describe('is new kommentar formular visible', () => { + it('should emit true', () => { + service._currentlyEdited$.next(EMPTY_STRING); + service.formularVisibility$.next(true); + + expect(service.isFormularVisible()).toBeObservable(singleCold(true)); + }); + + it('should emit false if any kommentar is being edited', () => { + service._currentlyEdited$.next(faker.internet.url()); + service.formularVisibility$.next(true); + + expect(service.isFormularVisible()).toBeObservable(singleCold(false)); + }); + + it('should emit false if not visible', () => { + service._currentlyEdited$.next(EMPTY_STRING); + service.formularVisibility$.next(false); + + expect(service.isFormularVisible()).toBeObservable(singleCold(false)); + }); + }); + + describe('show new kommentar formular', () => { + beforeEach(() => { + service.setCurrentlyEdited = jest.fn(); + }); + + it('should set currently edited to empty string', () => { + service.showFormular(); + + expect(service.setCurrentlyEdited).toHaveBeenCalledWith(EMPTY_STRING); + }); + + it('should set formular visibility', () => { + service.showFormular(); + + expect(service.formularVisibility$).toBeObservable(singleCold(true)); + }); + }); + + describe('set currently edited kommentar uri', () => { + beforeEach(() => { + service.clearUploadedFiles = jest.fn(); + service.hideFormular = jest.fn(); + }); + + it('should clear uploaded files', () => { + service.setCurrentlyEdited(faker.internet.url()); + + expect(service.clearUploadedFiles).toHaveBeenCalled(); + }); + + it('should hide kommentar creation formular', () => { + service.setCurrentlyEdited(faker.internet.url()); + + expect(service.hideFormular).toHaveBeenCalled(); + }); + + it('should NOT hide kommentar creation formular', () => { + service.setCurrentlyEdited(EMPTY_STRING); + + expect(service.hideFormular).not.toHaveBeenCalled(); + }); + + it('should emit currently edited resource uri', () => { + const resourceUri: ResourceUri = faker.internet.url(); + + service.setCurrentlyEdited(resourceUri); + + expect(service._currentlyEdited$).toBeObservable(singleCold(resourceUri)); + }); + }); }); diff --git a/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.ts b/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.ts index 1fd80c539fcdbb6c58223c3969a18e577f88d7e1..0055824e34836c3ad1195ce85d01ff4f2b772a35 100644 --- a/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.ts +++ b/alfa-client/libs/kommentar-shared/src/lib/kommentar.service.ts @@ -22,36 +22,27 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BinaryFileListResource, BinaryFileService } from '@alfa-client/binary-file-shared'; -import { - CommandOrder, - CommandResource, - CommandService, - CreateCommand, - isDone, -} from '@alfa-client/command-shared'; +import { CommandOrder, CommandResource, CommandService, CreateCommand, isDone } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { - StateResource, - createEmptyStateResource, - createStateResource, - doIfLoadingRequired, -} from '@alfa-client/tech-shared'; +import { createEmptyStateResource, createStateResource, doIfLoadingRequired, EMPTY_STRING, isNotEmpty, StateResource, } from '@alfa-client/tech-shared'; import { VorgangResource, VorgangService } from '@alfa-client/vorgang-shared'; import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; -import { Resource, hasLink } from '@ngxp/rest'; +import { hasLink, Resource, ResourceUri } from '@ngxp/rest'; import { isNil } from 'lodash-es'; -import { BehaviorSubject, Observable, Subscription, of } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { map, startWith, tap } from 'rxjs/operators'; import { KommentarLinkRel, KommentarListLinkRel } from './kommentar.linkrel'; -import { Kommentar, KommentarListResource, KommentarResource } from './kommentar.model'; +import { Kommentar, KOMMENTAR_UPLOADED_ATTACHMENTS, KommentarListResource, KommentarResource } from './kommentar.model'; import { KommentarRepository } from './kommentar.repository'; @Injectable({ providedIn: 'root' }) export class KommentarService { - readonly kommentarList$: BehaviorSubject<StateResource<KommentarListResource>> = - new BehaviorSubject(createEmptyStateResource<KommentarListResource>()); + readonly kommentarList$: BehaviorSubject<StateResource<KommentarListResource>> = new BehaviorSubject( + createEmptyStateResource<KommentarListResource>(), + ); readonly formularVisibility$: BehaviorSubject<boolean> = new BehaviorSubject(false); + readonly _currentlyEdited$: BehaviorSubject<ResourceUri> = new BehaviorSubject(EMPTY_STRING); private navigationSub: Subscription; @@ -67,9 +58,7 @@ export class KommentarService { private listenToNavigation(): void { this.unsubscribe(); - this.navigationSub = this.navigationService - .urlChanged() - .subscribe((params: Params) => this.onNavigation(params)); + this.navigationSub = this.navigationService.urlChanged().subscribe((params: Params) => this.onNavigation(params)); } private unsubscribe(): void { @@ -77,6 +66,8 @@ export class KommentarService { } onNavigation(params: Params): void { + this.clearUploadedFiles(); + this.hideFormular(); if (NavigationService.isVorgangListPage(params)) { this.setKommentarListOnReload(); } @@ -89,9 +80,7 @@ export class KommentarService { this.kommentarList$.next({ ...this.kommentarList$.value, reload: true }); } - public getKommentareByVorgang( - vorgang: VorgangResource, - ): Observable<StateResource<KommentarListResource>> { + public getKommentareByVorgang(vorgang: VorgangResource): Observable<StateResource<KommentarListResource>> { doIfLoadingRequired(this.kommentarList$.value, () => this.loadKommentare(vorgang)); return this.kommentarList$.asObservable(); } @@ -99,12 +88,10 @@ export class KommentarService { private loadKommentare(vorgang: VorgangResource): void { this.setListLoadingTrue(); - const sub: Subscription = this.repository - .findKommentare(vorgang) - .subscribe((kommentarList: KommentarListResource) => { - this.setKommentarList(kommentarList); - sub.unsubscribe(); - }); + const sub: Subscription = this.repository.findKommentare(vorgang).subscribe((kommentarList: KommentarListResource) => { + this.setKommentarList(kommentarList); + sub.unsubscribe(); + }); } setListLoadingTrue(): void { @@ -120,20 +107,19 @@ export class KommentarService { } public isFormularVisible(): Observable<boolean> { - return this.formularVisibility$.asObservable(); + return combineLatest([this.formularVisibility$, this._currentlyEdited$]).pipe( + map(([isVisible, currentlyEdited]) => isVisible && currentlyEdited === EMPTY_STRING), + ); } public canCreateNewKommentar(kommentareListResource: KommentarListResource): Observable<boolean> { return this.formularVisibility$.pipe( - map( - (formularVisibility) => - !formularVisibility && - hasLink(kommentareListResource, KommentarListLinkRel.CREATE_KOMMENTAR), - ), + map((formularVisibility) => !formularVisibility && hasLink(kommentareListResource, KommentarListLinkRel.CREATE_KOMMENTAR)), ); } public showFormular(): void { + this.setCurrentlyEdited(EMPTY_STRING); this.formularVisibility$.next(true); } @@ -153,15 +139,8 @@ export class KommentarService { return { order: CommandOrder.CREATE_KOMMENTAR, body: kommentar }; } - public editKommentar( - kommentar: KommentarResource, - toPatch: Kommentar, - ): Observable<StateResource<CommandResource>> { - return this.createKommentarCommand( - kommentar, - KommentarLinkRel.EDIT, - this.createEditKommentarCommand(toPatch), - ); + public editKommentar(kommentar: KommentarResource, toPatch: Kommentar): Observable<StateResource<CommandResource>> { + return this.createKommentarCommand(kommentar, KommentarLinkRel.EDIT, this.createEditKommentarCommand(toPatch)); } createEditKommentarCommand(kommentar: Kommentar): CreateCommand { @@ -174,9 +153,7 @@ export class KommentarService { command: CreateCommand, ): Observable<StateResource<CommandResource>> { return this.commandService.createCommand(resource, linkRel, command).pipe( - tap((createdCommand: StateResource<CommandResource>) => - this.afterCreateOrEditKommentar(createdCommand), - ), + tap((createdCommand: StateResource<CommandResource>) => this.afterCreateOrEditKommentar(createdCommand)), startWith(createEmptyStateResource<CommandResource>(true)), ); } @@ -195,4 +172,20 @@ export class KommentarService { } return of(createEmptyStateResource<BinaryFileListResource>()); } + + public clearUploadedFiles(): void { + this.binaryFileService.clearUploadedFiles(KOMMENTAR_UPLOADED_ATTACHMENTS); + } + + public getCurrentlyEdited(): Observable<ResourceUri> { + return this._currentlyEdited$.asObservable(); + } + + public setCurrentlyEdited(resourceUri: ResourceUri): void { + this.clearUploadedFiles(); + if (isNotEmpty(resourceUri)) { + this.hideFormular(); + } + this._currentlyEdited$.next(resourceUri); + } } diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.html b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.html index b6c374e27ae36f03b027cf262faa477281b79346..ccb901c94fa17fc98518b6abfdd9e202058c2e8a 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.html +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.html @@ -24,21 +24,17 @@ --> <form class="form" [formGroup]="formService.form"> - <ozgcloud-textarea-editor - [formControlName]="formServiceClass.TEXT" - label="Kommentar" - [required]="true" - > + <ozgcloud-textarea-editor [formControlName]="formServiceClass.TEXT" label="Kommentar" [required]="true"> </ozgcloud-textarea-editor> - <alfa-binary-file-attachment-container - data-test-id="kommentar-attachment-list" - [existFiles]="attachments$ | async" - [formArrayName]="formServiceClass.FIELD_ATTACHMENTS" - [uploadStateResource]="kommentarListStateResource" - [linkRelUploadAttachment]="kommentarListLinkRel.UPLOAD_FILE" - > - </alfa-binary-file-attachment-container> + <ods-multi-file-upload + [filesFormFieldName]="formServiceClass.FIELD_ATTACHMENTS" + [fileUploadType]="KOMMENTAR_UPLOADED_ATTACHMENTS" + [uploadResource]="kommentarListStateResource.resource" + [uploadLinkRelation]="kommentarListLinkRel.UPLOAD_FILE" + [filesResource]="kommentar" + [filesLinkRelation]="KommentarLinkRel.ATTACHMENTS" + ></ods-multi-file-upload> <div class="buttons"> <ozgcloud-stroked-button-with-spinner @@ -57,7 +53,7 @@ icon="clear" color="" class="cancel-button" - (clickEmitter)="cancel.emit()" + (clickEmitter)="onCancel()" > </ozgcloud-stroked-button-with-spinner> </div> diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.spec.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.spec.ts index a8af3d1f1dc2208e0502f3fef2dfa759083fc1ed..9753cc7fa2dafde144f13618d84c55a24b1bc1a3 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.spec.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.spec.ts @@ -21,21 +21,25 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { BinaryFileAttachmentContainerComponent } from '@alfa-client/binary-file'; +import { CommandResource } from '@alfa-client/command-shared'; +import { KommentarLinkRel, KommentarListResource, KommentarService } from '@alfa-client/kommentar-shared'; +import { createEmptyStateResource, createErrorStateResource, createStateResource, StateResource } from '@alfa-client/tech-shared'; +import { existsAsHtmlElement, Mock, mock, triggerEvent, useFromMock } from '@alfa-client/test-utils'; +import { OzgcloudStrokedButtonWithSpinnerComponent, TextAreaEditorComponent } from '@alfa-client/ui'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { BinaryFileAttachmentContainerComponent } from '@alfa-client/binary-file'; -import { KommentarLinkRel, KommentarService } from '@alfa-client/kommentar-shared'; -import { createStateResource } from '@alfa-client/tech-shared'; -import { mock } from '@alfa-client/test-utils'; -import { - OzgcloudStrokedButtonWithSpinnerComponent, - TextAreaEditorComponent, -} from '@alfa-client/ui'; +import { expect } from '@jest/globals'; import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; +import { EMPTY, of } from 'rxjs'; import { createBinaryFileListResource } from '../../../../../binary-file-shared/test/binary-file'; -import { createKommentarResource } from '../../../../../kommentar-shared/test/kommentar'; +import { MultiFileUploadComponent } from '../../../../../binary-file/src/lib/multi-file-upload/multi-file-upload.component'; +import { createSuccessfullyDoneCommandStateResource } from '../../../../../command-shared/test/command'; +import { createKommentarListResource, createKommentarResource } from '../../../../../kommentar-shared/test/kommentar'; +import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test'; +import { createProblemDetail } from '../../../../../tech-shared/test/error'; +import { singleColdCompleted } from '../../../../../tech-shared/test/marbles'; import { KommentarFormComponent } from './kommentar-form.component'; import { KommentarFormService } from './kommentar.formservice'; @@ -43,8 +47,17 @@ describe('KommentarFormComponent', () => { let component: KommentarFormComponent; let fixture: ComponentFixture<KommentarFormComponent>; - const formService = mock(KommentarFormService); - const kommentarService = mock(KommentarService); + const cancelButtonTestId: string = getDataTestIdOf('cancel-button'); + + const kommentarListStateResource: StateResource<KommentarListResource> = createStateResource(createKommentarListResource()); + + let kommentarService: Mock<KommentarService>; + let formService: KommentarFormService; + + beforeEach(() => { + formService = new KommentarFormService(new FormBuilder(), useFromMock(kommentarService)); + kommentarService = mock(KommentarService); + }); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -53,24 +66,31 @@ describe('KommentarFormComponent', () => { MockComponent(TextAreaEditorComponent), MockComponent(OzgcloudStrokedButtonWithSpinnerComponent), MockComponent(BinaryFileAttachmentContainerComponent), + MockComponent(MultiFileUploadComponent), ], imports: [MatFormFieldModule, ReactiveFormsModule], providers: [ - { - provide: KommentarFormService, - useValue: formService, - }, { provide: KommentarService, useValue: kommentarService, }, ], - }); - }); + }) + .overrideComponent(KommentarFormComponent, { + set: { + providers: [ + { + provide: KommentarFormService, + useValue: formService, + }, + ], + }, + }) + .compileComponents(); - beforeEach(() => { fixture = TestBed.createComponent(KommentarFormComponent); component = fixture.componentInstance; + component.kommentarListStateResource = kommentarListStateResource; fixture.detectChanges(); }); @@ -82,9 +102,7 @@ describe('KommentarFormComponent', () => { const patchSpy = jest.spyOn(KommentarFormService.prototype, 'patch').mockImplementation(); const kommentarResource = createKommentarResource([KommentarLinkRel.ATTACHMENTS]); component.kommentar = kommentarResource; - kommentarService.getAttachments.mockReturnValue( - of(createStateResource(createBinaryFileListResource())), - ); + kommentarService.getAttachments.mockReturnValue(of(createStateResource(createBinaryFileListResource()))); component.ngOnChanges(); @@ -93,9 +111,7 @@ describe('KommentarFormComponent', () => { it('should load attachments', (done) => { component.kommentar = createKommentarResource([KommentarLinkRel.ATTACHMENTS]); - kommentarService.getAttachments.mockReturnValue( - of(createStateResource(createBinaryFileListResource())), - ); + kommentarService.getAttachments.mockReturnValue(of(createStateResource(createBinaryFileListResource()))); component.ngOnChanges(); @@ -108,12 +124,94 @@ describe('KommentarFormComponent', () => { it('should call kommentarService', () => { const kommentarResource = createKommentarResource([KommentarLinkRel.ATTACHMENTS]); component.kommentar = kommentarResource; - kommentarService.getAttachments.mockReturnValue( - of(createStateResource(createBinaryFileListResource())), - ); + kommentarService.getAttachments.mockReturnValue(of(createStateResource(createBinaryFileListResource()))); component.ngOnChanges(); expect(kommentarService.getAttachments).toHaveBeenCalledWith(kommentarResource); }); + + describe('submit', () => { + it('should submit form', () => { + formService.submit = jest.fn().mockReturnValue(EMPTY); + + component.submit(); + + expect(formService.submit).toHaveBeenCalled(); + }); + + it('should set submit in progress', () => { + const command: StateResource<CommandResource> = createSuccessfullyDoneCommandStateResource(); + formService.submit = jest.fn().mockReturnValue(of(command)); + + component.submit(); + + expect(component.submitInProgress$).toBeObservable(singleColdCompleted(command)); + }); + + it('should clear uploaded attachments', () => { + formService.submit = jest.fn().mockReturnValue(of(createSuccessfullyDoneCommandStateResource())); + + component.submit(); + component.submitInProgress$.subscribe(); + + expect(kommentarService.clearUploadedFiles).toHaveBeenCalled(); + }); + + it('should NOT clear uploaded attachments on loading', () => { + formService.submit = jest.fn().mockReturnValue(of(createEmptyStateResource(true))); + + component.submit(); + component.submitInProgress$.subscribe(); + + expect(kommentarService.clearUploadedFiles).not.toHaveBeenCalled(); + }); + + it('should NOT clear uploaded attachments on error', () => { + formService.submit = jest.fn().mockReturnValue(of(createErrorStateResource(createProblemDetail()))); + + component.submit(); + component.submitInProgress$.subscribe(); + + expect(kommentarService.clearUploadedFiles).not.toHaveBeenCalled(); + }); + }); + + describe('onCancel', () => { + beforeEach(() => { + component.cancel.emit = jest.fn(); + }); + + it('should clear uploaded attachments', () => { + component.onCancel(); + + expect(kommentarService.clearUploadedFiles).toHaveBeenCalled(); + }); + + it('should emit', () => { + component.onCancel(); + + expect(component.cancel.emit).toHaveBeenCalled(); + }); + }); + + describe('template', () => { + describe('cancel button', () => { + it('should exists', () => { + existsAsHtmlElement(fixture, cancelButtonTestId); + }); + + describe('output', () => { + describe('clickEmitter', () => { + it('should call handler', () => { + component.onCancel = jest.fn(); + + triggerEvent({ fixture, elementSelector: cancelButtonTestId, name: 'clickEmitter' }); + + expect(component.onCancel).toHaveBeenCalled(); + }); + }); + }); + }); + }); }); diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.ts index 473d6a84137c0f56dac572f9992c6e6830c5ba44..0566d0f19efcc3e09aff3d5a726465eae21371d9 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component.ts @@ -21,24 +21,15 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { BinaryFileListLinkRel, BinaryFileResource } from '@alfa-client/binary-file-shared'; +import { CommandResource, tapOnCommandSuccessfullyDone } from '@alfa-client/command-shared'; +import { KOMMENTAR_UPLOADED_ATTACHMENTS, KommentarLinkRel, KommentarListLinkRel, KommentarListResource, KommentarResource, KommentarService, } from '@alfa-client/kommentar-shared'; +import { createEmptyStateResource, getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; -import { CommandResource } from '@alfa-client/command-shared'; -import { - KommentarListLinkRel, - KommentarListResource, - KommentarResource, - KommentarService, -} from '@alfa-client/kommentar-shared'; -import { - createEmptyStateResource, - getEmbeddedResources, - StateResource, -} from '@alfa-client/tech-shared'; import { isNil } from 'lodash-es'; import { Observable, of } from 'rxjs'; -import { KommentarFormService } from './kommentar.formservice'; -import { BinaryFileListLinkRel, BinaryFileResource } from '@alfa-client/binary-file-shared'; import { map } from 'rxjs/operators'; +import { KommentarFormService } from './kommentar.formservice'; @Component({ selector: 'alfa-kommentar-form', @@ -52,12 +43,12 @@ export class KommentarFormComponent implements OnChanges { @Output() cancel: EventEmitter<void> = new EventEmitter(); - submitInProgress$: Observable<StateResource<CommandResource>> = of( - createEmptyStateResource<CommandResource>(), - ); + submitInProgress$: Observable<StateResource<CommandResource>> = of(createEmptyStateResource<CommandResource>()); - readonly formServiceClass = KommentarFormService; - readonly kommentarListLinkRel = KommentarListLinkRel; + public readonly formServiceClass = KommentarFormService; + public readonly kommentarListLinkRel = KommentarListLinkRel; + public readonly KommentarLinkRel = KommentarLinkRel; + public readonly KOMMENTAR_UPLOADED_ATTACHMENTS = KOMMENTAR_UPLOADED_ATTACHMENTS; attachments$: Observable<BinaryFileResource[]> = of([]); @@ -76,18 +67,21 @@ export class KommentarFormComponent implements OnChanges { private updateAttachments() { this.attachments$ = this.kommentarService .getAttachments(this.kommentar) - .pipe( - map((stateResource) => - getEmbeddedResources<BinaryFileResource>(stateResource, BinaryFileListLinkRel.FILE_LIST), - ), - ); + .pipe(map((stateResource) => getEmbeddedResources<BinaryFileResource>(stateResource, BinaryFileListLinkRel.FILE_LIST))); } patch(): void { this.formService.patch(this.kommentar); } - submit(): void { - this.submitInProgress$ = <Observable<StateResource<CommandResource>>>this.formService.submit(); + public submit(): void { + this.submitInProgress$ = <Observable<StateResource<CommandResource>>>( + this.formService.submit().pipe(tapOnCommandSuccessfullyDone(() => this.kommentarService.clearUploadedFiles())) + ); + } + + public onCancel(): void { + this.kommentarService.clearUploadedFiles(); + this.cancel.emit(); } } diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.html b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.html index 8b432f5eee5589efc42249c5bfdf3f64a631b818..30f142acca7a7374f7e11d554c238f72a235b1ae 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.html +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.html @@ -27,6 +27,7 @@ <ozgcloud-expansion-panel headline="Kommentare"> <alfa-kommentar-list-in-vorgang [kommentarListStateResource]="kommentarListStateResource" + [currentlyEdited]="kommentarService.getCurrentlyEdited() | async" data-test-id="kommentar-list-in-vorgang" > </alfa-kommentar-list-in-vorgang> diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.spec.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.spec.ts index 44794b0072f9b661cd22120938e0a045e9b5d330..1fa11168673776f20130a7be5616abb0ac84380a 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.spec.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.spec.ts @@ -70,6 +70,14 @@ describe('KommentarListInVorgangContainerComponent', () => { expect(component).toBeTruthy(); }); + describe('ng on init', () => { + it('should call kommentar service isFormularVisible', () => { + component.ngOnInit(); + + expect(kommentarService.isFormularVisible).toHaveBeenCalled(); + }); + }); + describe('ng on changes', () => { beforeEach(() => { kommentarService.isFormularVisible.mockReturnValue(new Observable((o) => o.next(false))); @@ -78,12 +86,6 @@ describe('KommentarListInVorgangContainerComponent', () => { ); }); - it('should call kommentar service isFormularVisible', () => { - component.ngOnChanges(); - - expect(kommentarService.isFormularVisible).toHaveBeenCalled(); - }); - it('should call kommentar service getKommentareByVorgang', () => { component.ngOnChanges(); diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.ts index 8c601d9240a060ca4ceb9cd18a9b9964198eae83..21d3159a29496a6927f7076de4734ff03c663f44 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component.ts @@ -21,10 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input, OnChanges } from '@angular/core'; import { KommentarListResource, KommentarService } from '@alfa-client/kommentar-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; +import { Component, inject, Input, OnChanges, OnInit } from '@angular/core'; import { mergeMap, Observable } from 'rxjs'; @Component({ @@ -32,25 +32,24 @@ import { mergeMap, Observable } from 'rxjs'; templateUrl: './kommentar-list-in-vorgang-container.component.html', styleUrls: ['./kommentar-list-in-vorgang-container.component.scss'], }) -export class KommentarListInVorgangContainerComponent implements OnChanges { +export class KommentarListInVorgangContainerComponent implements OnChanges, OnInit { @Input() vorgangStateResource: StateResource<VorgangWithEingangResource>; - showFormular$: Observable<boolean>; + public readonly kommentarService = inject(KommentarService); + + public showFormular$: Observable<boolean>; kommentarListStateResource$: Observable<StateResource<KommentarListResource>>; canCreateNewKommentar$: Observable<boolean>; - constructor(private kommentarService: KommentarService) {} + ngOnInit(): void { + this.showFormular$ = this.kommentarService.isFormularVisible(); + } ngOnChanges(): void { this.reloadKommentarListOnVorgangReload(); - this.showFormular$ = this.kommentarService.isFormularVisible(); - this.kommentarListStateResource$ = this.kommentarService.getKommentareByVorgang( - this.vorgangStateResource.resource, - ); + this.kommentarListStateResource$ = this.kommentarService.getKommentareByVorgang(this.vorgangStateResource.resource); this.canCreateNewKommentar$ = this.kommentarListStateResource$.pipe( - mergeMap((stateResource) => - this.kommentarService.canCreateNewKommentar(stateResource.resource), - ), + mergeMap((stateResource) => this.kommentarService.canCreateNewKommentar(stateResource.resource)), ); } diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.html b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.html index be3ecc3720ffb2e967b888e54243eb72a5234c11..75ccfb30068aad3b5d8e2847618a3267cb0fb400 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.html +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.html @@ -27,5 +27,6 @@ *ngFor="let kommentar of kommentare" [kommentar]="kommentar" [kommentarListStateResource]="kommentarListStateResource" + [currentlyEdited]="currentlyEdited" > </alfa-kommentar-list-item-in-vorgang> diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.spec.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.spec.ts index 2e0e94f295129d7a65d7c9ca23c94ed384e83c38..4e267cefae258f6898249931a1b7495823d4e168 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.spec.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.spec.ts @@ -21,17 +21,13 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { KommentarListLinkRel } from '@alfa-client/kommentar-shared'; +import { createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockComponent } from 'ng-mocks'; +import { createKommentarListResource } from '../../../../../kommentar-shared/test/kommentar'; import { KommentarListInVorgangComponent } from './kommentar-list-in-vorgang.component'; import { KommentarListItemInVorgangComponent } from './kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component'; -import { - createEmptyStateResource, - createStateResource, - EMPTY_ARRAY, -} from '@alfa-client/tech-shared'; -import { createKommentarListResource } from '../../../../../kommentar-shared/test/kommentar'; -import { KommentarListLinkRel } from '@alfa-client/kommentar-shared'; describe('KommentarListInVorgangComponent', () => { let component: KommentarListInVorgangComponent; @@ -39,10 +35,7 @@ describe('KommentarListInVorgangComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - KommentarListInVorgangComponent, - MockComponent(KommentarListItemInVorgangComponent), - ], + declarations: [KommentarListInVorgangComponent, MockComponent(KommentarListItemInVorgangComponent)], }); }); @@ -60,22 +53,20 @@ describe('KommentarListInVorgangComponent', () => { it('should return empty array if state resource is null', () => { component.kommentarListStateResource = null; - expect(component.getKommentare()).toEqual(EMPTY_ARRAY); + expect(component.getKommentare()).toEqual([]); }); it('should return empty array if resource is null', () => { component.kommentarListStateResource = createEmptyStateResource(); - expect(component.getKommentare()).toEqual(EMPTY_ARRAY); + expect(component.getKommentare()).toEqual([]); }); it('should return embedded resource', () => { const kommentareListResource = createKommentarListResource(); component.kommentarListStateResource = createStateResource(kommentareListResource); - expect(component.getKommentare()).toEqual( - kommentareListResource._embedded[KommentarListLinkRel.KOMMENTAR_LIST], - ); + expect(component.getKommentare()).toEqual(kommentareListResource._embedded[KommentarListLinkRel.KOMMENTAR_LIST]); }); }); }); diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.ts index 4519d517cefe76995824afd7b45b8651ac354b8a..1d7212702a1b398d0da13637dc24e598d1f5bf8d 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component.ts @@ -21,10 +21,11 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input, OnChanges } from '@angular/core'; import { KommentarListResource, KommentarResource } from '@alfa-client/kommentar-shared'; -import { KommentarListLinkRel } from 'libs/kommentar-shared/src/lib/kommentar.linkrel'; import { getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; +import { Component, Input, OnChanges } from '@angular/core'; +import { ResourceUri } from '@ngxp/rest'; +import { KommentarListLinkRel } from 'libs/kommentar-shared/src/lib/kommentar.linkrel'; @Component({ selector: 'alfa-kommentar-list-in-vorgang', @@ -33,17 +34,15 @@ import { getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; }) export class KommentarListInVorgangComponent implements OnChanges { @Input() kommentarListStateResource: StateResource<KommentarListResource>; + @Input() currentlyEdited: ResourceUri; - kommentare: KommentarResource[]; + public kommentare: KommentarResource[]; ngOnChanges(): void { this.kommentare = this.getKommentare(); } getKommentare(): KommentarResource[] { - return getEmbeddedResources( - this.kommentarListStateResource, - KommentarListLinkRel.KOMMENTAR_LIST, - ); + return getEmbeddedResources(this.kommentarListStateResource, KommentarListLinkRel.KOMMENTAR_LIST); } } diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.html b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.html index e1e4dbdf6cd4e216ce96f189eed122d62ed74782..2bace6cd981024e6e589d527c6af06a0a1c572a1 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.html +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.html @@ -24,10 +24,7 @@ --> <div *ngIf="!editMode" class="plain-text text-sm"> - <button - [attr.data-test-id]="'kommentar-item-' + (kommentar.text | convertForDataTest)" - (click)="edit()" - > + <button [attr.data-test-id]="'kommentar-item-' + (kommentar.text | convertForDataTest)" (click)="edit()"> <div class="kommentar-head"> <alfa-user-profile-in-kommentar-container class="username" @@ -35,19 +32,19 @@ [kommentar]="kommentar" data-test-class="kommentar-created-by" ></alfa-user-profile-in-kommentar-container> - <span data-test-id="kommentar-created-at" class="date text-sm">{{ + <span data-test-class="kommentar-created-at" class="date text-sm">{{ kommentar.createdAt | formatDateWithTimePipe: false }}</span> </div> <p class="text">{{ kommentar.text }}</p> </button> - <alfa-horizontal-binary-file-list - *ngIf="kommentar | hasLink: kommentarLinkRel.ATTACHMENTS" - [deletable]="false" - [fileListResource]="attachments$ | async" - > - </alfa-horizontal-binary-file-list> + @if (kommentar | hasLink: kommentarLinkRel.ATTACHMENTS) { + <alfa-binary-file-list-container + [resource]="kommentar" + [linkRel]="kommentarLinkRel.ATTACHMENTS" + ></alfa-binary-file-list-container> + } </div> <alfa-kommentar-form diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.spec.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.spec.ts index 127b7fda5dd6f18ac336393aa425dd3903e94ee7..c5a1d190d2f95214dd39f60e95119fbda5d9e9ba 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.spec.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.spec.ts @@ -21,26 +21,17 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { KommentarLinkRel, KommentarResource, KommentarService } from '@alfa-client/kommentar-shared'; +import { ConvertForDataTestPipe, FormatDateWithTimePipe, HasLinkPipe } from '@alfa-client/tech-shared'; +import { getElementFromFixture, mock } from '@alfa-client/test-utils'; +import { UserProfileInKommentarContainerComponent } from '@alfa-client/user-profile'; import { registerLocaleData } from '@angular/common'; import localeDe from '@angular/common/locales/de'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { - KommentarLinkRel, - KommentarListLinkRel, - KommentarService, -} from '@alfa-client/kommentar-shared'; -import { - ConvertForDataTestPipe, - createStateResource, - FormatDateWithTimePipe, - HasLinkPipe, -} from '@alfa-client/tech-shared'; -import { getElementFromFixture, mock } from '@alfa-client/test-utils'; -import { UserProfileInKommentarContainerComponent } from '@alfa-client/user-profile'; -import { - createKommentarListResource, - createKommentarResource, -} from 'libs/kommentar-shared/test/kommentar'; +import { faker } from '@faker-js/faker/.'; +import { expect } from '@jest/globals'; +import { getUrl } from '@ngxp/rest'; +import { createKommentarResource } from 'libs/kommentar-shared/test/kommentar'; import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; import { KommentarFormComponent } from '../../kommentar-form/kommentar-form.component'; @@ -85,6 +76,37 @@ describe('KommentarListItemInVorgangComponent', () => { expect(component).toBeTruthy(); }); + describe('set currently edited', () => { + const kommentar: KommentarResource = createKommentarResource(); + + it('should set edit mode to false on same currently edited resource uri', () => { + component.editMode = false; + component.kommentar = kommentar; + + component.currentlyEdited = getUrl(kommentar); + + expect(component.editMode).toBe(false); + }); + + it('should set edit mode to false on different currently edited resource uri', () => { + component.editMode = true; + component.kommentar = kommentar; + + component.currentlyEdited = faker.internet.url(); + + expect(component.editMode).toBe(false); + }); + + it('should set edit mode to true', () => { + component.editMode = true; + component.kommentar = kommentar; + + component.currentlyEdited = getUrl(kommentar); + + expect(component.editMode).toBe(true); + }); + }); + describe('user profile', () => { it('should be visible on existing link', () => { component.kommentar = createKommentarResource([KommentarLinkRel.CREATED_BY]); @@ -105,18 +127,9 @@ describe('KommentarListItemInVorgangComponent', () => { }); }); - describe('kommentare attachments', () => { - it('should be loaded', () => { - const kommentarResource = createKommentarResource(); - component.kommentar = kommentarResource; - - component.ngOnInit(); - - expect(kommentarService.getAttachments).toHaveBeenCalledWith(kommentarResource); - }); - }); - describe('edit', () => { + const kommentar: KommentarResource = createKommentarResource(); + it('should change editMode', () => { component.kommentar = createKommentarResource([KommentarLinkRel.EDIT]); @@ -125,12 +138,20 @@ describe('KommentarListItemInVorgangComponent', () => { expect(component.editMode).toBeTruthy(); }); - it('should not change editMode', () => { - component.kommentar = createKommentarResource(); + it('should NOT change editMode', () => { + component.kommentar = kommentar; component.edit(); expect(component.editMode).toBeFalsy(); }); + + it('should set currently edited kommetar uri', () => { + component.kommentar = kommentar; + + component.edit(); + + expect(kommentarService.setCurrentlyEdited).toHaveBeenCalledWith(getUrl(kommentar)); + }); }); }); diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.ts b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.ts index d94fdd3c555be42544f58e660a91e7030edbac4d..7da6b91741e10ca74fdb3a4572d43190481826d5 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.ts @@ -21,42 +21,32 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input, OnInit } from '@angular/core'; -import { BinaryFileListResource } from '@alfa-client/binary-file-shared'; -import { - KommentarLinkRel, - KommentarListResource, - KommentarResource, - KommentarService, -} from '@alfa-client/kommentar-shared'; -import { StateResource, createEmptyStateResource } from '@alfa-client/tech-shared'; -import { hasLink } from '@ngxp/rest'; -import { Observable, of } from 'rxjs'; +import { KommentarLinkRel, KommentarListResource, KommentarResource, KommentarService } from '@alfa-client/kommentar-shared'; +import { StateResource } from '@alfa-client/tech-shared'; +import { Component, inject, Input } from '@angular/core'; +import { getUrl, hasLink, ResourceUri } from '@ngxp/rest'; @Component({ selector: 'alfa-kommentar-list-item-in-vorgang', templateUrl: './kommentar-list-item-in-vorgang.component.html', styleUrls: ['./kommentar-list-item-in-vorgang.component.scss'], }) -export class KommentarListItemInVorgangComponent implements OnInit { +export class KommentarListItemInVorgangComponent { @Input() kommentar: KommentarResource; - @Input() kommentarListStateResource: StateResource<KommentarListResource>; - attachments$: Observable<StateResource<BinaryFileListResource>> = of( - createEmptyStateResource<BinaryFileListResource>(), - ); - editMode: boolean = false; + @Input() set currentlyEdited(value: ResourceUri) { + this.editMode &&= value === getUrl(this.kommentar); + } - readonly kommentarLinkRel = KommentarLinkRel; + public kommentarService = inject(KommentarService); - constructor(public kommentarService: KommentarService) {} + public editMode: boolean = false; - ngOnInit(): void { - this.attachments$ = this.kommentarService.getAttachments(this.kommentar); - } + public readonly kommentarLinkRel = KommentarLinkRel; - edit(): void { + public edit(): void { this.editMode = hasLink(this.kommentar, KommentarLinkRel.EDIT); + this.kommentarService.setCurrentlyEdited(getUrl(this.kommentar)); } } diff --git a/alfa-client/libs/kommentar/src/lib/kommentar.module.ts b/alfa-client/libs/kommentar/src/lib/kommentar.module.ts index 4c9da054bee43f9411bd2e93ce3c4242246bf3aa..70d991da2a329dee09ccc3758fe80305d0347434 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar.module.ts +++ b/alfa-client/libs/kommentar/src/lib/kommentar.module.ts @@ -30,6 +30,9 @@ import { VorgangSharedModule } from '@alfa-client/vorgang-shared'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; +import { FileUploadListContainerComponent } from '../../../binary-file/src/lib/file-upload-list-container/file-upload-list-container.component'; +import { MultiFileUploadEditorComponent } from '../../../binary-file/src/lib/multi-file-upload-editor/multi-file-upload-editor.component'; +import { MultiFileUploadComponent } from '../../../binary-file/src/lib/multi-file-upload/multi-file-upload.component'; import { KommentarFormComponent } from './kommentar-list-in-vorgang-container/kommentar-form/kommentar-form.component'; import { KommentarListInVorgangContainerComponent } from './kommentar-list-in-vorgang-container/kommentar-list-in-vorgang-container.component'; import { KommentarListInVorgangComponent } from './kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-in-vorgang.component'; @@ -42,6 +45,9 @@ import { KommentarListItemInVorgangComponent } from './kommentar-list-in-vorgang KommentarSharedModule, UserProfileModule, BinaryFileModule, + MultiFileUploadEditorComponent, + FileUploadListContainerComponent, + MultiFileUploadComponent, ReactiveFormsModule, TextAreaEditorComponent, OzgcloudStrokedButtonWithSpinnerComponent, diff --git a/alfa-client/libs/postfach-shared/src/lib/postfach.service.spec.ts b/alfa-client/libs/postfach-shared/src/lib/postfach.service.spec.ts index f26deaae319cf2f62a1dc3c0aebce901dff4709d..678219f735fd5882a763a535687688c630bd5045 100644 --- a/alfa-client/libs/postfach-shared/src/lib/postfach.service.spec.ts +++ b/alfa-client/libs/postfach-shared/src/lib/postfach.service.spec.ts @@ -21,40 +21,21 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - BinaryFileListResource, - BinaryFileResource, - BinaryFileService, -} from '@alfa-client/binary-file-shared'; +import { BinaryFileService } from '@alfa-client/binary-file-shared'; import { CommandResource, CommandService } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { - StateResource, - createEmptyStateResource, - createStateResource, -} from '@alfa-client/tech-shared'; -import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; +import { createEmptyStateResource, createStateResource, StateResource } from '@alfa-client/tech-shared'; +import { Mock, mock } from '@alfa-client/test-utils'; import { SnackBarService } from '@alfa-client/ui'; import { VorgangService, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; +import { TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; -import { - createBinaryFileListResource, - createBinaryFileResource, -} from 'libs/binary-file-shared/test/binary-file'; +import { expect } from '@jest/globals'; import { CommandLinkRel } from 'libs/command-shared/src/lib/command.linkrel'; -import { - createCommandErrorResource, - createCommandResource, -} from 'libs/command-shared/test/command'; +import { createCommandErrorResource, createCommandResource } from 'libs/command-shared/test/command'; import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; import { BehaviorSubject, of } from 'rxjs'; -import { - createPostfachFeatures, - createPostfachMail, - createPostfachMailListResource, - createPostfachMailResource, - createPostfachSettings, -} from '../../test/postfach'; +import { createPostfachFeatures, createPostfachMail, createPostfachMailListResource, createPostfachMailResource, createPostfachSettings, } from '../../test/postfach'; import { PostfachFacade } from './+state/postfach.facade'; import { PostfachMailLinkRel, PostfachMailListLinkRel } from './postfach.linkrel'; import { PostfachMessages } from './postfach.message'; @@ -72,8 +53,8 @@ describe('PostfachService', () => { const vorgangService: Mock<VorgangService> = mock(VorgangService); const snackbarService: Mock<SnackBarService> = mock(SnackBarService); const dialog: Mock<MatDialog> = <Mock<MatDialog>>{ closeAll: jest.fn() }; - const binaryFileService: Mock<BinaryFileService> = mock(BinaryFileService); const postfachFacade: Mock<PostfachFacade> = mock(PostfachFacade); + const binaryFileService: Mock<BinaryFileService> = mock(BinaryFileService); const urlChangedParams = {}; @@ -83,16 +64,21 @@ describe('PostfachService', () => { repository = mock(PostfachRepository); - service = new PostfachService( - useFromMock(repository), - useFromMock(commandService), - useFromMock(navigationService), - useFromMock(vorgangService), - useFromMock(snackbarService), - useFromMock(dialog), - useFromMock(binaryFileService), - useFromMock(postfachFacade), - ); + TestBed.configureTestingModule({ + providers: [ + { provide: CommandService, useValue: commandService }, + { provide: NavigationService, useValue: navigationService }, + { provide: VorgangService, useValue: vorgangService }, + { provide: SnackBarService, useValue: snackbarService }, + { provide: MatDialog, useValue: dialog }, + { provide: PostfachFacade, useValue: postfachFacade }, + { provide: BinaryFileService, useValue: binaryFileService }, + { provide: PostfachRepository, useValue: repository }, + PostfachService, + ], + }); + + service = TestBed.inject(PostfachService); }); it('should be created', () => { @@ -101,8 +87,7 @@ describe('PostfachService', () => { describe('send message', () => { const postfachMail: PostfachMail = createPostfachMail(); - const commandStateResource: StateResource<CommandResource> = - createStateResource(createCommandResource()); + const commandStateResource: StateResource<CommandResource> = createStateResource(createCommandResource()); beforeEach(() => { commandService.createCommand.mockReturnValue(of(commandStateResource)); @@ -144,9 +129,7 @@ describe('PostfachService', () => { }); it('should not execute on pending command', () => { - commandService.createCommand.mockReturnValue( - of(createStateResource(createCommandResource([CommandLinkRel.UPDATE]))), - ); + commandService.createCommand.mockReturnValue(of(createStateResource(createCommandResource([CommandLinkRel.UPDATE])))); service.sendMail(postfachMail); @@ -159,8 +142,7 @@ describe('PostfachService', () => { const postfachNachricht: PostfachMailResource = createPostfachMailResource(); const nachricht: PostfachMail = createPostfachMail(); - const commandStateResource: StateResource<CommandResource> = - createStateResource(createCommandResource()); + const commandStateResource: StateResource<CommandResource> = createStateResource(createCommandResource()); it('should call doSendNachricht', () => { service.doSendNachricht = jest.fn(); @@ -183,12 +165,8 @@ describe('PostfachService', () => { beforeEach(() => { service.handleSendPostfachMailIsDone = jest.fn(); - commandService.createCommand.mockReturnValue( - of(createStateResource(createCommandResource())), - ); - vorgangService.getPendingSendPostfachMailCommand.mockReturnValue( - of(createStateResource(createCommandResource())), - ); + commandService.createCommand.mockReturnValue(of(createStateResource(createCommandResource()))); + vorgangService.getPendingSendPostfachMailCommand.mockReturnValue(of(createStateResource(createCommandResource()))); }); it('should call vorgang service and set command on loading', () => { @@ -210,9 +188,7 @@ describe('PostfachService', () => { }); it('should NOT call handleSendPostfachMailIsDone if command is pending', () => { - commandService.createCommand.mockReturnValue( - of(createStateResource(createCommandResource([CommandLinkRel.UPDATE]))), - ); + commandService.createCommand.mockReturnValue(of(createStateResource(createCommandResource([CommandLinkRel.UPDATE])))); service.resendMail(postfachMailResource); expect(service.handleSendPostfachMailIsDone).not.toHaveBeenCalled(); @@ -231,8 +207,7 @@ describe('PostfachService', () => { }); describe('handleSendNachrichtCommand', () => { - const commandStateResource: StateResource<CommandResource> = - createStateResource(createCommandResource()); + const commandStateResource: StateResource<CommandResource> = createStateResource(createCommandResource()); beforeEach(() => { service.commandIsDone = jest.fn(); @@ -241,9 +216,7 @@ describe('PostfachService', () => { it('should call vorgang service', () => { service.handleSendNachrichtCommand(commandStateResource); - expect(vorgangService.setPendingSendPostfachMailCommand).toHaveBeenCalledWith( - commandStateResource, - ); + expect(vorgangService.setPendingSendPostfachMailCommand).toHaveBeenCalledWith(commandStateResource); }); it('should call commandIsDone', () => { @@ -277,10 +250,7 @@ describe('PostfachService', () => { service.commandIsDone(commandStateResource); - expect(snackbarService.show).toHaveBeenCalledWith( - commandStateResource.resource, - PostfachMessages.SEND_SUCCESSFUL, - ); + expect(snackbarService.show).toHaveBeenCalledWith(commandStateResource.resource, PostfachMessages.SEND_SUCCESSFUL); }); }); @@ -392,9 +362,7 @@ describe('PostfachService', () => { service.pollSendPostfachMailCommand = jest.fn(); commandService.pollCommand.mockReturnValue(of(createStateResource(createCommandResource()))); - vorgangService.getPendingSendPostfachMailCommand.mockReturnValue( - of(createStateResource(createCommandResource())), - ); + vorgangService.getPendingSendPostfachMailCommand.mockReturnValue(of(createStateResource(createCommandResource()))); }); it('should call navigation service', () => { @@ -405,15 +373,12 @@ describe('PostfachService', () => { }); describe('resetHasNewPostfachNachrichten', () => { - const postfachListNachrichtenResource: PostfachMailListResource = - createPostfachMailListResource(); + const postfachListNachrichtenResource: PostfachMailListResource = createPostfachMailListResource(); it('should call doResetHasNewPostfachNachrichten', () => { service.getPostfachMailListByVorgang = jest.fn(); service.doResetHasNewPostfachNachrichten = jest.fn(); - (<any>service.getPostfachMailListByVorgang).mockReturnValue( - of(postfachListNachrichtenResource), - ); + (<any>service.getPostfachMailListByVorgang).mockReturnValue(of(postfachListNachrichtenResource)); service.resetHasNewPostfachNachrichten(); @@ -423,28 +388,24 @@ describe('PostfachService', () => { describe('doResetHasNewPostfachNachrichten', () => { describe('on existing link', () => { - const postfachNachrichtenListResource: PostfachMailListResource = - createPostfachMailListResource([PostfachMailListLinkRel.RESET_HAS_NEW_POSTFACH_NACHRICHT]); + const postfachNachrichtenListResource: PostfachMailListResource = createPostfachMailListResource([ + PostfachMailListLinkRel.RESET_HAS_NEW_POSTFACH_NACHRICHT, + ]); beforeEach(() => { service.postfachMailList$.next(createStateResource(postfachNachrichtenListResource)); - repository.resetHasNewPostfachNachrichten.mockReturnValue( - of(postfachNachrichtenListResource), - ); + repository.resetHasNewPostfachNachrichten.mockReturnValue(of(postfachNachrichtenListResource)); }); it('should call repository if link exists', () => { service.doResetHasNewPostfachNachrichten(); - expect(repository.resetHasNewPostfachNachrichten).toHaveBeenCalledWith( - postfachNachrichtenListResource, - ); + expect(repository.resetHasNewPostfachNachrichten).toHaveBeenCalledWith(postfachNachrichtenListResource); }); }); it('should NOT call repository if link not exists', () => { - const postfachNachrichtenListResource: PostfachMailListResource = - createPostfachMailListResource(); + const postfachNachrichtenListResource: PostfachMailListResource = createPostfachMailListResource(); service.postfachMailList$.next(createStateResource(postfachNachrichtenListResource)); service.doResetHasNewPostfachNachrichten(); @@ -460,9 +421,7 @@ describe('PostfachService', () => { repository.loadPostfachMailList.mockReturnValue(of(postfachMailList)); service.setPostfachMailListLoading = jest.fn(); service.setPostfachMailList = jest.fn(); - vorgangService.getVorgangWithEingang.mockReturnValue( - of(createStateResource(createVorgangWithEingangResource())), - ); + vorgangService.getVorgangWithEingang.mockReturnValue(of(createStateResource(createVorgangWithEingangResource()))); service.postfachMailList$.next(createEmptyStateResource()); }); @@ -492,8 +451,7 @@ describe('PostfachService', () => { }); describe('handleSendPostfachMailIsDone', () => { - const stateResource: StateResource<CommandResource> = - createStateResource(createCommandResource()); + const stateResource: StateResource<CommandResource> = createStateResource(createCommandResource()); beforeEach(() => { service.setPollingFalse = jest.fn(); @@ -521,26 +479,6 @@ describe('PostfachService', () => { }); }); - describe('loadAttachments', () => { - const postfachNachricht: PostfachMailResource = createPostfachMailResource(); - const fileListStateResource: StateResource<BinaryFileListResource> = createStateResource( - createBinaryFileListResource(), - ); - - beforeEach(() => { - binaryFileService.getFiles.mockReturnValue(of(fileListStateResource)); - }); - - it('should call binaryFile service with resource', () => { - service.loadAttachments(postfachNachricht); - - expect(binaryFileService.getFiles).toHaveBeenCalledWith( - postfachNachricht, - PostfachMailLinkRel.ATTACHMENTS, - ); - }); - }); - describe('isDownloadPdfInProgress', () => { const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource(); @@ -582,54 +520,9 @@ describe('PostfachService', () => { }); }); - describe('getAttachments', () => { - const postfachNachricht: PostfachMailResource = createPostfachMailResource(); - const binaryFileResource: BinaryFileResource = createBinaryFileResource(); - const BinaryFileListResource: BinaryFileListResource = createBinaryFileListResource([ - binaryFileResource, - ]); - - beforeEach(() => { - postfachFacade.getAttachmentList.mockReturnValue( - of(createStateResource(BinaryFileListResource)), - ); - }); - - it('should get attachments', () => { - service.getAttachments(postfachNachricht); - - expect(postfachFacade.getAttachmentList).toHaveBeenCalled(); - }); - - it('should load attachments if loading is required', () => { - postfachFacade.getAttachmentList.mockReturnValue(of(createEmptyStateResource())); - - service.getAttachments(postfachNachricht).subscribe(); - - expect(postfachFacade.loadAttachmentList).toHaveBeenCalledWith(postfachNachricht); - }); - - it('should return value', (done) => { - service.getAttachments(postfachNachricht).subscribe((attachments: BinaryFileResource[]) => { - expect(attachments).toEqual([binaryFileResource]); - done(); - }); - }); - }); - - describe('clearAttachmentList', () => { - it('should call facade', () => { - service.clearAttachmentList(); - - expect(postfachFacade.clearAttachmentList).toHaveBeenCalled(); - }); - }); - describe('getFeatures', () => { it('should return features by list stateResource', (done) => { - service.postfachMailList$ = new BehaviorSubject( - createStateResource(createPostfachMailListResource()), - ); + service.postfachMailList$ = new BehaviorSubject(createStateResource(createPostfachMailListResource())); service.getFeatures().subscribe((features) => { expect(features).toEqual(createPostfachFeatures()); @@ -640,9 +533,7 @@ describe('PostfachService', () => { describe('getSettings', () => { it('should return settings by list stateResource', (done) => { - service.postfachMailList$ = new BehaviorSubject( - createStateResource(createPostfachMailListResource()), - ); + service.postfachMailList$ = new BehaviorSubject(createStateResource(createPostfachMailListResource())); service.getSettings().subscribe((settings) => { expect(settings).toEqual(createPostfachSettings()); @@ -650,4 +541,16 @@ describe('PostfachService', () => { }); }); }); + + describe('close open dialogs', () => { + beforeEach(() => { + service.clearUploadedFiles = jest.fn(); + }); + + it('should clear uploaded files', () => { + service.closeOpenDialogs(); + + expect(service.clearUploadedFiles).toHaveBeenCalled(); + }); + }); }); diff --git a/alfa-client/libs/postfach-shared/src/lib/postfach.service.ts b/alfa-client/libs/postfach-shared/src/lib/postfach.service.ts index 483377d3c54a2d43db97f549a428b1d00312593c..e39019766d5f873a246643dec3c951cc4e2efd39 100644 --- a/alfa-client/libs/postfach-shared/src/lib/postfach.service.ts +++ b/alfa-client/libs/postfach-shared/src/lib/postfach.service.ts @@ -21,57 +21,39 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - BinaryFileListResource, - BinaryFileResource, - BinaryFileService, - getBinaryFiles, -} from '@alfa-client/binary-file-shared'; -import { - CommandResource, - CommandService, - doIfCommandIsDone, - hasCommandError, - isDone, - isPending, -} from '@alfa-client/command-shared'; +import { POSTFACH_NACHRICHT_UPLOADED_ATTACHMENTS } from '@admin-client/postfach-shared'; +import { BinaryFileService } from '@alfa-client/binary-file-shared'; +import { CommandResource, CommandService, doIfCommandIsDone, hasCommandError, isDone, isPending, } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { - StateResource, - createEmptyStateResource, - createStateResource, - doIfLoadingRequired, - isNotNull, - isNotUndefined, -} from '@alfa-client/tech-shared'; +import { createEmptyStateResource, createStateResource, doIfLoadingRequired, isNotNull, isNotUndefined, StateResource, } from '@alfa-client/tech-shared'; import { SnackBarService } from '@alfa-client/ui'; import { VorgangResource, VorgangService } from '@alfa-client/vorgang-shared'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Params } from '@angular/router'; -import { Resource, hasLink } from '@ngxp/rest'; +import { hasLink, Resource } from '@ngxp/rest'; import { isNil, isNull } from 'lodash-es'; -import { BehaviorSubject, Observable, Subscription, combineLatest } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; import { first, map, take, tap } from 'rxjs/operators'; import { PostfachFacade } from './+state/postfach.facade'; import { PostfachMailLinkRel, PostfachMailListLinkRel } from './postfach.linkrel'; import { PostfachMessages } from './postfach.message'; -import { - CreatePostfachMailCommand, - PostfachFeatures, - PostfachMail, - PostfachMailListResource, - PostfachMailResource, - PostfachSettings, -} from './postfach.model'; +import { CreatePostfachMailCommand, PostfachFeatures, PostfachMail, PostfachMailListResource, PostfachMailResource, PostfachSettings, } from './postfach.model'; import { PostfachRepository } from './postfach.repository'; import { createResendPostfachMailCommand, createSendPostfachMailCommand } from './postfach.util'; @Injectable({ providedIn: 'root' }) export class PostfachService { - private readonly isPollSendPostachMail: BehaviorSubject<boolean> = new BehaviorSubject<boolean>( - false, - ); + private readonly repository = inject(PostfachRepository); + private readonly commandService = inject(CommandService); + private readonly navigationService = inject(NavigationService); + private readonly vorgangService = inject(VorgangService); + private readonly snackbarService = inject(SnackBarService); + private readonly dialog = inject(MatDialog); + private readonly postfachFacade = inject(PostfachFacade); + private readonly binaryFileService = inject(BinaryFileService); + + private readonly isPollSendPostachMail: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); postfachMailList$: BehaviorSubject<StateResource<PostfachMailListResource>> = new BehaviorSubject< StateResource<PostfachMailListResource> >(createEmptyStateResource<PostfachMailListResource>()); @@ -83,16 +65,7 @@ export class PostfachService { private vorgangSubscription: Subscription; private postfachNachrichtenListSubscription: Subscription; - constructor( - private repository: PostfachRepository, - private commandService: CommandService, - private navigationService: NavigationService, - private vorgangService: VorgangService, - private snackbarService: SnackBarService, - private dialog: MatDialog, - private binaryFileService: BinaryFileService, - private postfachFacade: PostfachFacade, - ) { + constructor() { this.listenToNavigation(); } @@ -108,16 +81,10 @@ export class PostfachService { postfachMailResource: PostfachMailResource, postfachMail: PostfachMail, ): Observable<StateResource<CommandResource>> { - return this.doSendNachricht( - postfachMailResource, - PostfachMailLinkRel.SEND, - createSendPostfachMailCommand(postfachMail), - ); + return this.doSendNachricht(postfachMailResource, PostfachMailLinkRel.SEND, createSendPostfachMailCommand(postfachMail)); } - public resendMail( - postfachMailResource: PostfachMailResource, - ): Observable<StateResource<CommandResource>> { + public resendMail(postfachMailResource: PostfachMailResource): Observable<StateResource<CommandResource>> { return this.doSendNachricht( postfachMailResource, PostfachMailLinkRel.RESEND_POSTFACH_MAIL, @@ -168,9 +135,7 @@ export class PostfachService { listenToNavigation(): void { this.unsubscribeToNavigation(); - this.navigationSubscription = this.navigationService - .urlChanged() - .subscribe((params) => this.onNavigation(params)); + this.navigationSubscription = this.navigationService.urlChanged().subscribe((params) => this.onNavigation(params)); } unsubscribeToNavigation(): void { @@ -202,13 +167,12 @@ export class PostfachService { closeOpenDialogs(): void { this.dialog.closeAll(); + this.clearUploadedFiles(); } unsubscribe(): void { - if (isNotUndefined(this.sendPostfachMailSubscription)) - this.sendPostfachMailSubscription.unsubscribe(); - if (isNotUndefined(this.loadPostfachMailSubscription)) - this.loadPostfachMailSubscription.unsubscribe(); + if (isNotUndefined(this.sendPostfachMailSubscription)) this.sendPostfachMailSubscription.unsubscribe(); + if (isNotUndefined(this.loadPostfachMailSubscription)) this.loadPostfachMailSubscription.unsubscribe(); if (isNotUndefined(this.vorgangSubscription)) this.vorgangSubscription.unsubscribe(); } @@ -217,45 +181,31 @@ export class PostfachService { } resetHasNewPostfachNachrichten(): void { - this.postfachNachrichtenListSubscription = this.getPostfachMailListByVorgang().subscribe( - (postfachNachrichtenList) => { - if (isNotNull(postfachNachrichtenList.resource)) { - setTimeout(() => this.postfachNachrichtenListSubscription.unsubscribe(), 0); - this.doResetHasNewPostfachNachrichten(); - } - }, - ); + this.postfachNachrichtenListSubscription = this.getPostfachMailListByVorgang().subscribe((postfachNachrichtenList) => { + if (isNotNull(postfachNachrichtenList.resource)) { + setTimeout(() => this.postfachNachrichtenListSubscription.unsubscribe(), 0); + this.doResetHasNewPostfachNachrichten(); + } + }); } doResetHasNewPostfachNachrichten(): void { - if ( - hasLink( - this.postfachMailList$.value.resource, - PostfachMailListLinkRel.RESET_HAS_NEW_POSTFACH_NACHRICHT, - ) - ) { - this.repository - .resetHasNewPostfachNachrichten(this.postfachMailList$.value.resource) - .pipe(take(1)) - .subscribe(); + if (hasLink(this.postfachMailList$.value.resource, PostfachMailListLinkRel.RESET_HAS_NEW_POSTFACH_NACHRICHT)) { + this.repository.resetHasNewPostfachNachrichten(this.postfachMailList$.value.resource).pipe(take(1)).subscribe(); } } - pollSendPostfachMailCommand( - command: StateResource<CommandResource>, - ): StateResource<CommandResource> { + pollSendPostfachMailCommand(command: StateResource<CommandResource>): StateResource<CommandResource> { if (this.shouldPoll(command)) { this.setPollingTrue(); - this.sendPostfachMailSubscription = this.commandService - .pollCommand(command.resource) - .subscribe((updatedStateResource) => { - this.vorgangService.setPendingSendPostfachMailCommand(updatedStateResource); - - if (isDone(updatedStateResource.resource)) { - this.handleSendPostfachMailIsDone(updatedStateResource); - setTimeout(() => this.sendPostfachMailSubscription.unsubscribe(), 0); - } - }); + this.sendPostfachMailSubscription = this.commandService.pollCommand(command.resource).subscribe((updatedStateResource) => { + this.vorgangService.setPendingSendPostfachMailCommand(updatedStateResource); + + if (isDone(updatedStateResource.resource)) { + this.handleSendPostfachMailIsDone(updatedStateResource); + setTimeout(() => this.sendPostfachMailSubscription.unsubscribe(), 0); + } + }); } return command; } @@ -282,17 +232,11 @@ export class PostfachService { }); } - getEffectedResource( - updatedStateResource: StateResource<CommandResource>, - ): Observable<PostfachMailListResource> { - return this.commandService.getEffectedResource<PostfachMailListResource>( - updatedStateResource.resource, - ); + getEffectedResource(updatedStateResource: StateResource<CommandResource>): Observable<PostfachMailListResource> { + return this.commandService.getEffectedResource<PostfachMailListResource>(updatedStateResource.resource); } - public getPostfachMailListByGivenVorgang( - vorgang: VorgangResource, - ): Observable<StateResource<PostfachMailListResource>> { + public getPostfachMailListByGivenVorgang(vorgang: VorgangResource): Observable<StateResource<PostfachMailListResource>> { doIfLoadingRequired(this.postfachMailList$.value, () => { this.setPostfachMailListLoading(); this.loadPostfachMailsByVorgang(vorgang); @@ -303,14 +247,12 @@ export class PostfachService { public getPostfachMailListByVorgang(): Observable<StateResource<PostfachMailListResource>> { doIfLoadingRequired(this.postfachMailList$.value, () => { this.setPostfachMailListLoading(); - this.vorgangSubscription = this.vorgangService - .getVorgangWithEingang() - .subscribe((vorgangWithEingangStateResource) => { - if (vorgangWithEingangStateResource.resource) { - this.loadPostfachMailsByVorgang(vorgangWithEingangStateResource.resource); - setTimeout(() => this.vorgangSubscription.unsubscribe(), 0); - } - }); + this.vorgangSubscription = this.vorgangService.getVorgangWithEingang().subscribe((vorgangWithEingangStateResource) => { + if (vorgangWithEingangStateResource.resource) { + this.loadPostfachMailsByVorgang(vorgangWithEingangStateResource.resource); + setTimeout(() => this.vorgangSubscription.unsubscribe(), 0); + } + }); }); return this.postfachMailList$.asObservable(); } @@ -320,31 +262,20 @@ export class PostfachService { } public loadPostfachMailsByVorgang(vorgang: VorgangResource): void { - this.loadPostfachMailSubscription = this.repository - .loadPostfachMailList(vorgang) - .subscribe((postfachMaiList) => { - if (!isNull(postfachMaiList)) { - this.setPostfachMailList(postfachMaiList); - setTimeout(() => this.loadPostfachMailSubscription.unsubscribe(), 0); - } - }); + this.loadPostfachMailSubscription = this.repository.loadPostfachMailList(vorgang).subscribe((postfachMaiList) => { + if (!isNull(postfachMaiList)) { + this.setPostfachMailList(postfachMaiList); + setTimeout(() => this.loadPostfachMailSubscription.unsubscribe(), 0); + } + }); } setPostfachMailList(postfachMailList: PostfachMailListResource): void { this.postfachMailList$.next(createStateResource(postfachMailList)); } - public loadAttachments( - postfachNachricht: PostfachMailResource, - ): Observable<StateResource<BinaryFileListResource>> { - return this.binaryFileService.getFiles(postfachNachricht, PostfachMailLinkRel.ATTACHMENTS); - } - public isDownloadPdfInProgress(): Observable<boolean> { - return combineLatest([ - this.vorgangService.getVorgangWithEingang(), - this.postfachFacade.isDownloadPdfInProgress(), - ]).pipe( + return combineLatest([this.vorgangService.getVorgangWithEingang(), this.postfachFacade.isDownloadPdfInProgress()]).pipe( tap(([vorgang, isDownloadInProgress]) => { if (isDownloadInProgress && vorgang.resource) { this.postfachFacade.downloadPdf(vorgang.resource); @@ -358,36 +289,19 @@ export class PostfachService { this.postfachFacade.startDownloadPdf(); } - public getAttachments(postfachNachricht: PostfachMailResource): Observable<BinaryFileResource[]> { - return this.postfachFacade.getAttachmentList().pipe( - tap((attachmentList) => - doIfLoadingRequired(attachmentList, () => - this.postfachFacade.loadAttachmentList(postfachNachricht), - ), - ), - map(getBinaryFiles), - ); - } - - public clearAttachmentList(): void { - this.postfachFacade.clearAttachmentList(); - } - public getFeatures(): Observable<PostfachFeatures> { return this.getPostfachMailListByVorgang().pipe( - map( - (listStateResource: StateResource<PostfachMailListResource>) => - listStateResource.resource.features, - ), + map((listStateResource: StateResource<PostfachMailListResource>) => listStateResource.resource.features), ); } public getSettings(): Observable<PostfachSettings> { return this.getPostfachMailListByVorgang().pipe( - map( - (listStateResource: StateResource<PostfachMailListResource>) => - listStateResource.resource.settings, - ), + map((listStateResource: StateResource<PostfachMailListResource>) => listStateResource.resource.settings), ); } + + public clearUploadedFiles(): void { + this.binaryFileService.clearUploadedFiles(POSTFACH_NACHRICHT_UPLOADED_ATTACHMENTS); + } } diff --git a/alfa-client/libs/postfach-shared/test/postfach.ts b/alfa-client/libs/postfach-shared/test/postfach.ts index cc96efe9a93fdde304d17e460277bd4a146d429a..831f9b7eeafd5401719b392b56d1a79b2c0cb212 100644 --- a/alfa-client/libs/postfach-shared/test/postfach.ts +++ b/alfa-client/libs/postfach-shared/test/postfach.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { EMPTY_ARRAY, createStateResource } from '@alfa-client/tech-shared'; +import { createStateResource } from '@alfa-client/tech-shared'; import { faker } from '@faker-js/faker'; import { Resource } from '@ngxp/rest'; import { toResource } from 'libs/tech-shared/test/resource'; @@ -61,18 +61,14 @@ export function createPostfachMailResources(linkRelations: string[] = []): Postf return times(10, () => toResource(createPostfachMailResource(), [...linkRelations])); } -export function createPostfachMailListResource( - linkRelations: string[] = [], -): PostfachMailListResource { +export function createPostfachMailListResource(linkRelations: string[] = []): PostfachMailListResource { return toResource(createPostfachMailList(), [...linkRelations], { [PostfachMailListLinkRel.POSTFACH_MAIL_LIST]: createPostfachMailResources(), }); } function createPostfachMailList(): Resource { - return toResource({ features: createPostfachFeatures(), settings: createPostfachSettings() }, [ - 'sendPostfachMail', - ]); + return toResource({ features: createPostfachFeatures(), settings: createPostfachSettings() }, ['sendPostfachMail']); } export function createPostfachFeatures(): PostfachFeatures { @@ -100,10 +96,7 @@ export class PostfachTestFactory { attachments: faker.string.uuid(), }; - public static POSTFACH_NACHRICHT_RESOURCE: PostfachMailResource = toResource( - PostfachTestFactory.POSTFACH_NACHRICHT, - EMPTY_ARRAY, - ); + public static POSTFACH_NACHRICHT_RESOURCE: PostfachMailResource = toResource(PostfachTestFactory.POSTFACH_NACHRICHT, []); public static POSTFACH_NACHRICHT_LIST_RESOURCE = createPostfachMailListResource(); public static POSTFACH_NACHRICHT_LIST_STATE_RESOURCE = createStateResource( PostfachTestFactory.POSTFACH_NACHRICHT_LIST_RESOURCE, diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-button-container/postfach-mail-button-container.component.spec.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-button-container/postfach-mail-button-container.component.spec.ts index 7ac87155bd62df629b16f7b90a8485f833e7cea5..e520088dd7a78278b713934277034613dd2d9cff 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-button-container/postfach-mail-button-container.component.spec.ts +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-button-container/postfach-mail-button-container.component.spec.ts @@ -22,23 +22,15 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { PostfachMailFormComponent } from '@alfa-client/postfach'; -import { - PostfachMailFormDialogData, - PostfachMailListResource, - PostfachService, -} from '@alfa-client/postfach-shared'; -import { - HasLinkPipe, - StateResource, - createErrorStateResource, - createStateResource, -} from '@alfa-client/tech-shared'; +import { PostfachMailFormDialogData, PostfachMailListResource, PostfachService } from '@alfa-client/postfach-shared'; +import { createErrorStateResource, createStateResource, HasLinkPipe, StateResource } from '@alfa-client/tech-shared'; import { mock } from '@alfa-client/test-utils'; import { DialogService } from '@alfa-client/ui'; import { VorgangHeaderLinkRel, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; import { getEmpfaenger } from '@alfa-client/vorgang-shared-ui'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { faker } from '@faker-js/faker'; +import { expect } from '@jest/globals'; import { createCommandResource } from 'libs/command-shared/test/command'; import { createPostfachMailListResource } from 'libs/postfach-shared/test/postfach'; import { createApiError } from 'libs/tech-shared/test/error'; @@ -62,11 +54,7 @@ describe('PostfachMailButtonContainerComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - PostfachMailButtonContainerComponent, - HasLinkPipe, - MockComponent(PostfachMailButtonComponent), - ], + declarations: [PostfachMailButtonContainerComponent, HasLinkPipe, MockComponent(PostfachMailButtonComponent)], providers: [ { provide: DialogService, @@ -92,9 +80,7 @@ describe('PostfachMailButtonContainerComponent', () => { }); describe('vorgang changes', () => { - const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource([ - VorgangHeaderLinkRel.POSTFACH_MAILS, - ]); + const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource([VorgangHeaderLinkRel.POSTFACH_MAILS]); beforeEach(() => { component.loadPendingSendPostfachMailCommand = jest.fn(); @@ -117,9 +103,7 @@ describe('PostfachMailButtonContainerComponent', () => { describe('loadPendingSendPostfachMailCommand', () => { beforeEach(() => { component.closeDialog = jest.fn(); - postfachService.getPendingSendPostfachMailCommand.mockReturnValue( - of(createStateResource(createCommandResource())), - ); + postfachService.getPendingSendPostfachMailCommand.mockReturnValue(of(createStateResource(createCommandResource()))); }); it('should call postfach service', () => { @@ -136,9 +120,7 @@ describe('PostfachMailButtonContainerComponent', () => { }); describe('loadPostfachMailListStateResource', () => { - const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource([ - VorgangHeaderLinkRel.POSTFACH_MAILS, - ]); + const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource([VorgangHeaderLinkRel.POSTFACH_MAILS]); beforeEach(() => { postfachService.getPostfachMailListByGivenVorgang.mockReturnValue(of({})); @@ -157,11 +139,21 @@ describe('PostfachMailButtonContainerComponent', () => { const postfachMailListStateResource: StateResource<PostfachMailListResource> = createStateResource(createPostfachMailListResource()); + beforeEach(() => { + dialogService.openFixed.mockReturnValue({ afterClosed: jest.fn().mockReturnValue(of(null)) }); + }); + it('should open new dialog with given data', () => { component.openPostfachMailDialog(postfachMailListStateResource); expect(dialogService.openFixed).toHaveBeenCalled(); }); + + it('should clear upload files after dialog was closed', () => { + component.openPostfachMailDialog(postfachMailListStateResource); + + expect(postfachService.clearUploadedFiles).toHaveBeenCalled(); + }); }); describe('closeDialog', () => { @@ -197,9 +189,7 @@ describe('PostfachMailButtonContainerComponent', () => { const empfanger: string = faker.person.firstName(); getEmpfaengerMock.mockReturnValue(empfanger); - const dialogData: PostfachMailFormDialogData = component.buildDialogData( - postfachMailListStateResource, - ); + const dialogData: PostfachMailFormDialogData = component.buildDialogData(postfachMailListStateResource); expect(dialogData.empfaenger).toEqual(empfanger); }); @@ -211,9 +201,7 @@ describe('PostfachMailButtonContainerComponent', () => { }); it('should set title', () => { - const dialogData: PostfachMailFormDialogData = component.buildDialogData( - postfachMailListStateResource, - ); + const dialogData: PostfachMailFormDialogData = component.buildDialogData(postfachMailListStateResource); expect(dialogData.title).toEqual(PostfachMailButtonContainerComponent.TITLE); }); diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-button-container/postfach-mail-button-container.component.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-button-container/postfach-mail-button-container.component.ts index 8670a6d75fb62befcdc6d7a3e07c0ac2b677107f..5810ca247015c57bbed5ea36e94c45c05c287601 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-button-container/postfach-mail-button-container.component.ts +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-button-container/postfach-mail-button-container.component.ts @@ -22,20 +22,15 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { CommandResource } from '@alfa-client/command-shared'; -import { - PostfachMailFormDialogData, - PostfachMailListLinkRel, - PostfachMailListResource, - PostfachService, -} from '@alfa-client/postfach-shared'; -import { StateResource, hasStateResourceError, isNotNull, isNotUndefined } from '@alfa-client/tech-shared'; +import { PostfachMailFormDialogData, PostfachMailListLinkRel, PostfachMailListResource, PostfachService, } from '@alfa-client/postfach-shared'; +import { hasStateResourceError, isNotNull, isNotUndefined, StateResource } from '@alfa-client/tech-shared'; import { DialogService, FixedDialogComponent } from '@alfa-client/ui'; import { VorgangHeaderLinkRel, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; import { getEmpfaenger } from '@alfa-client/vorgang-shared-ui'; import { Component, Input } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; import { hasLink } from '@ngxp/rest'; -import { Observable } from 'rxjs'; +import { first, Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; import { PostfachMailFormComponent } from '../postfach-mail-form/postfach-mail-form.component'; @@ -51,6 +46,7 @@ export class PostfachMailButtonContainerComponent { public get vorgang(): VorgangWithEingangResource { return this._vorgang; } + @Input() public set vorgang(value: VorgangWithEingangResource) { this._vorgang = value; @@ -59,6 +55,7 @@ export class PostfachMailButtonContainerComponent { this.loadPendingSendPostfachMailCommand(); } } + @Input() showAsIconButton: boolean; @Input() text: string; @Input() toolTip: string; @@ -76,9 +73,7 @@ export class PostfachMailButtonContainerComponent { ) {} loadPostfachMailListStateResource(): void { - this.postfachMailListStateResource$ = this.postfachService.getPostfachMailListByGivenVorgang( - this.vorgang, - ); + this.postfachMailListStateResource$ = this.postfachService.getPostfachMailListByGivenVorgang(this.vorgang); } loadPendingSendPostfachMailCommand(): void { @@ -88,26 +83,20 @@ export class PostfachMailButtonContainerComponent { } closeDialog(commandStateResource: StateResource<CommandResource>): void { - if ( - !hasStateResourceError(commandStateResource) && - commandStateResource.loaded && - isNotUndefined(this.dialogRef) - ) { + if (!hasStateResourceError(commandStateResource) && commandStateResource.loaded && isNotUndefined(this.dialogRef)) { this.dialogService.closeAll(); } } - public openPostfachMailDialog( - postfachMailListStateResource: StateResource<PostfachMailListResource>, - ): void { - this.dialogRef = this.dialogService.openFixed( - this.buildDialogData(postfachMailListStateResource), - ); + public openPostfachMailDialog(postfachMailListStateResource: StateResource<PostfachMailListResource>): void { + this.dialogRef = this.dialogService.openFixed(this.buildDialogData(postfachMailListStateResource)); + this.dialogRef + .afterClosed() + .pipe(first()) + .subscribe(() => this.postfachService.clearUploadedFiles()); } - buildDialogData( - postfachMailListStateResource: StateResource<PostfachMailListResource>, - ): PostfachMailFormDialogData { + buildDialogData(postfachMailListStateResource: StateResource<PostfachMailListResource>): PostfachMailFormDialogData { return { component: PostfachMailFormComponent, title: PostfachMailButtonContainerComponent.TITLE, diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.html b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.html index 187cd25d070f87a75faa3b08db3ce643671ac439..56e1c490b8b2e01e5644eda045e3f210d56eb1de 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.html +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.html @@ -45,9 +45,14 @@ > </ozgcloud-textarea-editor> - <alfa-postfach-nachricht-attachment-container - [postfachNachricht]="dialogData.postfachNachricht" - ></alfa-postfach-nachricht-attachment-container> + <ods-multi-file-upload + [filesFormFieldName]="formServiceClass.FIELD_ATTACHMENTS" + [fileUploadType]="POSTFACH_NACHRICHT_UPLOADED_ATTACHMENTS" + [uploadResource]="dialogData.postfachMailListStateResource.resource" + [uploadLinkRelation]="PostfachMailListLinkRel.UPLOAD_ATTACHMENT" + [filesResource]="dialogData.postfachNachricht" + [filesLinkRelation]="PostfachMailLinkRel.ATTACHMENTS" + ></ods-multi-file-upload> <div class="button-bar-bottom space-between"> <ozgcloud-stroked-button-with-spinner diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.spec.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.spec.ts index 5b5beec8bf365ee4a043970eb471f592faeb4ed7..6eac1a58938ad4d04061bc0d05a29d00dcd61d7d 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.spec.ts +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.spec.ts @@ -24,15 +24,7 @@ import { PostfachService } from '@alfa-client/postfach-shared'; import { createStateResource } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; -import { - DialogService, - FileUploadComponent, - IconButtonWithSpinnerComponent, - OzgcloudStrokedButtonWithSpinnerComponent, - OzgcloudTextEditorComponent, - SpinnerComponent, - TextAreaEditorComponent, -} from '@alfa-client/ui'; +import { DialogService, FileUploadComponent, IconButtonWithSpinnerComponent, OzgcloudStrokedButtonWithSpinnerComponent, OzgcloudTextEditorComponent, SpinnerComponent, TextAreaEditorComponent, } from '@alfa-client/ui'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; @@ -40,14 +32,14 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { createCommandResource } from 'libs/command-shared/test/command'; -import { PostfachTestFactory, createPostfachSettings } from 'libs/postfach-shared/test/postfach'; +import { createPostfachSettings, PostfachTestFactory } from 'libs/postfach-shared/test/postfach'; import { MockComponent } from 'ng-mocks'; import { of } from 'rxjs'; import { PostfachMailFormComponent } from './postfach-mail-form.component'; import { PostfachMailFormservice } from './postfach-mail.formservice'; -import { PostfachNachrichtAttachmentContainerComponent } from './postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component'; import { PostfachNachrichtReplyEditorContainerComponent } from './postfach-nachricht-reply-editor-container/postfach-nachricht-reply-editor-container.component'; +import { MultiFileUploadComponent } from '../../../../binary-file/src/lib/multi-file-upload/multi-file-upload.component'; import * as CommandUtil from '../../../../command-shared/src/lib/command.util'; describe('PostfachMailFormComponent', () => { @@ -72,7 +64,7 @@ describe('PostfachMailFormComponent', () => { MockComponent(PostfachNachrichtReplyEditorContainerComponent), MockComponent(SpinnerComponent), MockComponent(FileUploadComponent), - MockComponent(PostfachNachrichtAttachmentContainerComponent), + MockComponent(MultiFileUploadComponent), ], imports: [MatFormFieldModule, MatInputModule, ReactiveFormsModule, BrowserAnimationsModule], providers: [ diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.ts index ca025479a0a9cecc88383647a3864487d9db8017..e5c1c3514301810c4a832ac3b16952bc5e22515f 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.ts +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.ts @@ -21,16 +21,16 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { CommandResource } from '@alfa-client/command-shared'; -import { PostfachMailFormDialogData } from '@alfa-client/postfach-shared'; -import { StateResource, createEmptyStateResource, isNotNil } from '@alfa-client/tech-shared'; +import { CommandResource, isSuccessfulDone } from '@alfa-client/command-shared'; +import { PostfachMailFormDialogData, PostfachMailLinkRel, PostfachMailListLinkRel } from '@alfa-client/postfach-shared'; +import { createEmptyStateResource, isNotNil, StateResource } from '@alfa-client/tech-shared'; import { DialogService } from '@alfa-client/ui'; import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; import { Observable, of, tap } from 'rxjs'; import { PostfachMailFormservice } from './postfach-mail.formservice'; -import * as CommandUtil from '../../../../command-shared/src/lib/command.util'; +import { POSTFACH_NACHRICHT_UPLOADED_ATTACHMENTS } from '@admin-client/postfach-shared'; @Component({ selector: 'alfa-postfach-mail-form', @@ -41,9 +41,11 @@ import * as CommandUtil from '../../../../command-shared/src/lib/command.util'; export class PostfachMailFormComponent implements OnInit { public readonly formServiceClass = PostfachMailFormservice; - public sendInProgress$: Observable<StateResource<CommandResource>> = of( - createEmptyStateResource<CommandResource>(), - ); + public sendInProgress$: Observable<StateResource<CommandResource>> = of(createEmptyStateResource<CommandResource>()); + + public readonly PostfachMailListLinkRel = PostfachMailListLinkRel; + public readonly PostfachMailLinkRel = PostfachMailLinkRel; + public readonly POSTFACH_NACHRICHT_UPLOADED_ATTACHMENTS = POSTFACH_NACHRICHT_UPLOADED_ATTACHMENTS; constructor( private dialogService: DialogService, @@ -65,14 +67,12 @@ export class PostfachMailFormComponent implements OnInit { this.sendInProgress$ = this.formService .submit() .pipe( - tap((commandStateResource: StateResource<CommandResource>) => - this.closeDialogsOnSuccessfulSubmit(commandStateResource), - ), + tap((commandStateResource: StateResource<CommandResource>) => this.closeDialogsOnSuccessfulSubmit(commandStateResource)), ); } closeDialogsOnSuccessfulSubmit(commandStateResource: StateResource<CommandResource>): void { - if (CommandUtil.isSuccessfulDone(commandStateResource.resource)) { + if (isSuccessfulDone(commandStateResource.resource)) { this.dialogService.closeAll(); } } diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.html b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.html deleted file mode 100644 index c907494c101d39d7b8e9e7cf6adf26eaced8363b..0000000000000000000000000000000000000000 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.html +++ /dev/null @@ -1,32 +0,0 @@ -<!-- - - Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<alfa-binary-file-attachment-container - [formArrayName]="formServiceClass.FIELD_ATTACHMENTS" - [existFiles]="attachments$ | async" - [uploadStateResource]="postfachNachrichtListStateResource$ | async" - [linkRelUploadAttachment]="postfachMailListLinkRel.UPLOAD_ATTACHMENT" -> -</alfa-binary-file-attachment-container> diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.scss b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.scss deleted file mode 100644 index 54c4f3eb8c92af93694c03cdf577fed23cf9f86b..0000000000000000000000000000000000000000 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.scss +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.spec.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.spec.ts deleted file mode 100644 index 61948618124f3cb14813d3ba6a1cf365345e1d78..0000000000000000000000000000000000000000 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.spec.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { BinaryFileAttachmentContainerComponent } from '@alfa-client/binary-file'; -import { BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { - PostfachMailListLinkRel, - PostfachMailListResource, - PostfachService, -} from '@alfa-client/postfach-shared'; -import { StateResource, createStateResource } from '@alfa-client/tech-shared'; -import { Mock, getMockComponent, mock } from '@alfa-client/test-utils'; -import { createBinaryFileResource } from 'libs/binary-file-shared/test/binary-file'; -import { - PostfachTestFactory, - createPostfachMailListResource, -} from 'libs/postfach-shared/test/postfach'; -import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; -import { PostfachMailFormservice } from '../postfach-mail.formservice'; -import { PostfachNachrichtAttachmentContainerComponent } from './postfach-nachricht-attachment-container.component'; - -describe('PostfachNachrichtAttachmentContainerComponent', () => { - let component: PostfachNachrichtAttachmentContainerComponent; - let fixture: ComponentFixture<PostfachNachrichtAttachmentContainerComponent>; - - const service: Mock<PostfachService> = mock(PostfachService); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - PostfachNachrichtAttachmentContainerComponent, - MockComponent(BinaryFileAttachmentContainerComponent), - ], - providers: [ - { - provide: PostfachService, - useValue: service, - }, - ], - }).compileComponents(); - - fixture = TestBed.createComponent(PostfachNachrichtAttachmentContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('ngOnInit', () => { - beforeEach(() => { - component.initAttachments = jest.fn(); - }); - - it('should get list stateResource', () => { - component.ngOnInit(); - - expect(service.getPostfachMailListByVorgang).toHaveBeenCalled(); - }); - - it('should initAttachments on existing postfachNachricht', () => { - component.postfachNachricht = PostfachTestFactory.POSTFACH_NACHRICHT_RESOURCE; - - component.ngOnInit(); - - expect(component.initAttachments).toHaveBeenCalled(); - }); - - it('should not init attachments on missing postfachNachricht', () => { - component.postfachNachricht = undefined; - - component.ngOnInit(); - - expect(component.initAttachments).not.toHaveBeenCalled(); - }); - }); - - describe('binary file attachment container', () => { - it('should be called with formArrayName', () => { - const binaryFileAttachmentContainer: BinaryFileAttachmentContainerComponent = - getMockComponent(fixture, BinaryFileAttachmentContainerComponent); - expect(binaryFileAttachmentContainer.formArrayName).toBe( - PostfachMailFormservice.FIELD_ATTACHMENTS, - ); - }); - - it('should be called with existFiles', () => { - const attachments: BinaryFileResource[] = [createBinaryFileResource()]; - component.attachments$ = of(attachments); - - fixture.detectChanges(); - - const binaryFileAttachmentContainer: BinaryFileAttachmentContainerComponent = - getMockComponent(fixture, BinaryFileAttachmentContainerComponent); - expect(binaryFileAttachmentContainer.existFiles).toBe(attachments); - }); - - it('should be called with upload stateResource', () => { - const listStateResource: StateResource<PostfachMailListResource> = createStateResource( - createPostfachMailListResource(), - ); - component.postfachNachrichtListStateResource$ = of(listStateResource); - - fixture.detectChanges(); - - const binaryFileAttachmentContainer: BinaryFileAttachmentContainerComponent = - getMockComponent(fixture, BinaryFileAttachmentContainerComponent); - expect(binaryFileAttachmentContainer.uploadStateResource).toBe(listStateResource); - }); - - it('should be called with linkRel upload attachment', () => { - const binaryFileAttachmentContainer: BinaryFileAttachmentContainerComponent = - getMockComponent(fixture, BinaryFileAttachmentContainerComponent); - expect(binaryFileAttachmentContainer.linkRelUploadAttachment).toBe( - PostfachMailListLinkRel.UPLOAD_ATTACHMENT, - ); - }); - }); - - describe('ngOnDestroy', () => { - it('should clear attachments', () => { - component.ngOnDestroy(); - - expect(service.clearAttachmentList).toHaveBeenCalled(); - }); - }); -}); diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.ts deleted file mode 100644 index ff5ef4cdaa751d6a7c746fee18c23aef08a0a527..0000000000000000000000000000000000000000 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { Component, Input, OnDestroy } from '@angular/core'; -import { BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { - PostfachMailLinkRel, - PostfachMailListLinkRel, - PostfachMailListResource, - PostfachMailResource, - PostfachService, -} from '@alfa-client/postfach-shared'; -import { EMPTY_ARRAY, FormProvider, StateResource, isNotNil } from '@alfa-client/tech-shared'; -import { hasLink } from '@ngxp/rest'; -import { Observable, of } from 'rxjs'; -import { PostfachMailFormservice } from '../postfach-mail.formservice'; - -@Component({ - selector: 'alfa-postfach-nachricht-attachment-container', - templateUrl: './postfach-nachricht-attachment-container.component.html', - styleUrls: ['./postfach-nachricht-attachment-container.component.scss'], - viewProviders: [FormProvider], -}) -export class PostfachNachrichtAttachmentContainerComponent implements OnDestroy { - @Input() postfachNachricht: PostfachMailResource; - - postfachNachrichtListStateResource$: Observable<StateResource<PostfachMailListResource>>; - attachments$: Observable<BinaryFileResource[]> = of<BinaryFileResource[]>(EMPTY_ARRAY); - - public readonly postfachMailListLinkRel = PostfachMailListLinkRel; - public readonly formServiceClass = PostfachMailFormservice; - - constructor(private postfachService: PostfachService) {} - - ngOnInit(): void { - this.postfachNachrichtListStateResource$ = this.postfachService.getPostfachMailListByVorgang(); - - if (isNotNil(this.postfachNachricht)) { - this.initAttachments(); - } - } - - initAttachments(): void { - if (hasLink(this.postfachNachricht, PostfachMailLinkRel.ATTACHMENTS)) { - this.attachments$ = this.postfachService.getAttachments(this.postfachNachricht); - } - } - - ngOnDestroy(): void { - this.postfachService.clearAttachmentList(); - } -} diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.html b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.html deleted file mode 100644 index 0c91966fbcdcafe6c47f4d31cfd9557b6fbdf7d3..0000000000000000000000000000000000000000 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.html +++ /dev/null @@ -1,27 +0,0 @@ -<!-- - - Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<alfa-vertical-binary-file-list [binaryFileListStateResource]="attachments$ | async"> -</alfa-vertical-binary-file-list> diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.scss b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.scss deleted file mode 100644 index 87ec96c5bc70794e99e27966a5de96750909f80d..0000000000000000000000000000000000000000 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.scss +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -.files { - display: flex; - flex-wrap: wrap; - margin: 4px -4px; - flex-direction: row; - max-width: 100%; - align-items: flex-start; -} diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.spec.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.spec.ts deleted file mode 100644 index 8556f6a6ce7f3b8b95a34ec9a387e04817c367b1..0000000000000000000000000000000000000000 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { registerLocaleData } from '@angular/common'; -import localeDe from '@angular/common/locales/de'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { BinaryFileListResource } from '@alfa-client/binary-file-shared'; -import { PostfachMailResource, PostfachService } from '@alfa-client/postfach-shared'; -import { ConvertForDataTestPipe, createStateResource } from '@alfa-client/tech-shared'; -import { Mock, mock } from '@alfa-client/test-utils'; -import { SpinnerComponent } from '@alfa-client/ui'; -import { createBinaryFileListResource } from 'libs/binary-file-shared/test/binary-file'; -import { createPostfachMailResource } from 'libs/postfach-shared/test/postfach'; -import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; -import { PostfachMailAttachmentsComponent } from './postfach-mail-attachments.component'; -import { VerticalBinaryFileListComponent } from '@alfa-client/binary-file'; - -registerLocaleData(localeDe); - -describe('PostfachMailAttachmentsComponent', () => { - let component: PostfachMailAttachmentsComponent; - let fixture: ComponentFixture<PostfachMailAttachmentsComponent>; - - const postfachNachricht: PostfachMailResource = createPostfachMailResource(); - - const postfachService: Mock<PostfachService> = mock(PostfachService); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - PostfachMailAttachmentsComponent, - ConvertForDataTestPipe, - MockComponent(VerticalBinaryFileListComponent), - MockComponent(SpinnerComponent), - ], - providers: [ - { - provide: PostfachService, - useValue: postfachService, - }, - ], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(PostfachMailAttachmentsComponent); - component = fixture.componentInstance; - component.postfachNachricht = postfachNachricht; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('ngOnChanges', () => { - const attachmentList: BinaryFileListResource = createBinaryFileListResource(); - - beforeEach(() => { - postfachService.loadAttachments.mockReturnValue(of(createStateResource(attachmentList))); - }); - - it('should call service', () => { - component.ngOnChanges(); - - expect(postfachService.loadAttachments).toHaveBeenCalledWith(postfachNachricht); - }); - }); -}); diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.ts deleted file mode 100644 index b88457f2c228477973d769cbfa164f7da8f09b31..0000000000000000000000000000000000000000 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { Component, Input, OnChanges } from '@angular/core'; -import { BinaryFileListResource } from '@alfa-client/binary-file-shared'; -import { PostfachMailResource, PostfachService } from '@alfa-client/postfach-shared'; -import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared'; -import { Observable, of } from 'rxjs'; - -@Component({ - selector: 'alfa-postfach-mail-attachments', - templateUrl: './postfach-mail-attachments.component.html', - styleUrls: ['./postfach-mail-attachments.component.scss'], -}) -export class PostfachMailAttachmentsComponent implements OnChanges { - @Input() postfachNachricht: PostfachMailResource; - - attachments$: Observable<StateResource<BinaryFileListResource>> = of( - createEmptyStateResource<BinaryFileListResource>(), - ); - - constructor(private postfachService: PostfachService) {} - - ngOnChanges(): void { - this.attachments$ = this.postfachService.loadAttachments(this.postfachNachricht); - } -} diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.html b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.html index b87d049868c0c54e15b1e20eb351dca3c483ad98..b9a11494ce9e63d735f2829904d138b8670a1eb3 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.html +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.html @@ -37,9 +37,11 @@ > </alfa-outgoing-mail> -<alfa-postfach-mail-attachments - *ngIf="(postfachMail | hasLink: postfachNachrichtLinkRel.ATTACHMENTS) && onPage" - data-test-id="postfach-mail-attachments-container" - [postfachNachricht]="postfachMail" -> -</alfa-postfach-mail-attachments> + +@if((postfachMail | hasLink: postfachNachrichtLinkRel.ATTACHMENTS) && onPage){ +<alfa-binary-file-list-container + [resource]="postfachMail" + [linkRel]="PostfachMailLinkRel.ATTACHMENTS" + [listOrientation]="BinaryFileListOrientation.VERTICAL" + data-test-id="postfach-mail-attachments-container"></alfa-binary-file-list-container> +} diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.spec.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.spec.ts index 02fedf3b44d53fe51c05ef688c6f638b831ced6c..24c3fe590a36fde09fb1acb5b0a5fa1bf214982f 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.spec.ts +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.spec.ts @@ -21,27 +21,18 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { - Direction, - ON_PAGE, - PostfachMailLinkRel, - PostfachMailResource, -} from '@alfa-client/postfach-shared'; -import { HasLinkPipe, StateResource, createStateResource } from '@alfa-client/tech-shared'; -import { - existsAsHtmlElement, - getMockComponent, - notExistsAsHtmlElement, -} from '@alfa-client/test-utils'; +import { BinaryFileListContainerComponent } from '@alfa-client/binary-file'; +import { Direction, ON_PAGE, PostfachMailLinkRel, PostfachMailResource } from '@alfa-client/postfach-shared'; +import { createStateResource, HasLinkPipe, StateResource } from '@alfa-client/tech-shared'; +import { existsAsHtmlElement, getMockComponent, notExistsAsHtmlElement } from '@alfa-client/test-utils'; import { VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { createPostfachMailResource } from 'libs/postfach-shared/test/postfach'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; import { MockComponent } from 'ng-mocks'; import { IncommingMailComponent } from './incomming-mail/incomming-mail.component'; import { OutgoingMailComponent } from './outgoing-mail/outgoing-mail.component'; -import { PostfachMailAttachmentsComponent } from './postfach-mail-attachments/postfach-mail-attachments.component'; import { PostfachMailComponent } from './postfach-mail.component'; describe('PostfachMailComponent', () => { @@ -50,9 +41,7 @@ describe('PostfachMailComponent', () => { const postfachIncomingNachricht: string = getDataTestIdOf('postfach-incoming-nachricht'); const postfachOutgoingNachricht: string = getDataTestIdOf('postfach-outgoing-nachricht'); - const postfachNachrichtAttachments: string = getDataTestIdOf( - 'postfach-mail-attachments-container', - ); + const postfachNachrichtAttachments: string = getDataTestIdOf('postfach-mail-attachments-container'); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -61,7 +50,7 @@ describe('PostfachMailComponent', () => { PostfachMailComponent, MockComponent(IncommingMailComponent), MockComponent(OutgoingMailComponent), - MockComponent(PostfachMailAttachmentsComponent), + MockComponent(BinaryFileListContainerComponent), ], providers: [ { @@ -94,9 +83,7 @@ describe('PostfachMailComponent', () => { }); describe('on existing link and page', () => { - const postfachNachricht: PostfachMailResource = createPostfachMailResource([ - PostfachMailLinkRel.ATTACHMENTS, - ]); + const postfachNachricht: PostfachMailResource = createPostfachMailResource([PostfachMailLinkRel.ATTACHMENTS]); beforeEach(() => { component.onPage = true; @@ -108,17 +95,6 @@ describe('PostfachMailComponent', () => { existsAsHtmlElement(fixture, postfachNachrichtAttachments); }); - - it('should be called with postfachNachricht', () => { - fixture.detectChanges(); - - const postfachAttachmentsComponent: PostfachMailAttachmentsComponent = getMockComponent( - fixture, - PostfachMailAttachmentsComponent, - ); - - expect(postfachAttachmentsComponent.postfachNachricht).toBe(postfachNachricht); - }); }); }); @@ -140,19 +116,13 @@ describe('PostfachMailComponent', () => { describe('call', () => { it('should be with postfachNachricht', () => { - const outgoingMailComponent: OutgoingMailComponent = getMockComponent( - fixture, - OutgoingMailComponent, - ); + const outgoingMailComponent: OutgoingMailComponent = getMockComponent(fixture, OutgoingMailComponent); expect(outgoingMailComponent.postfachMail).toBe(outgoingPostfachNachricht); }); it('should be with vorgangStateResource', () => { - const outgoingMailComponent: OutgoingMailComponent = getMockComponent( - fixture, - OutgoingMailComponent, - ); + const outgoingMailComponent: OutgoingMailComponent = getMockComponent(fixture, OutgoingMailComponent); expect(outgoingMailComponent.vorgangStateResource).toBe(vorgangStateResource); }); @@ -194,10 +164,7 @@ describe('PostfachMailComponent', () => { describe('call', () => { it('should be with postfachNachricht', () => { - const incomingMailComponent: IncommingMailComponent = getMockComponent( - fixture, - IncommingMailComponent, - ); + const incomingMailComponent: IncommingMailComponent = getMockComponent(fixture, IncommingMailComponent); expect(incomingMailComponent.postfachMail).toBe(incomingPostfachNachricht); }); diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.ts b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.ts index 25bde83011fce2fe074aa2945a06894458bcb4e0..50cb1511c6d09b34bb4b19a65e1d1d5eb5508440 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.ts +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component.ts @@ -21,12 +21,8 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - ON_PAGE, - PostfachMailLinkRel, - PostfachMailResource, - isIncomingMail, -} from '@alfa-client/postfach-shared'; +import { BinaryFileListOrientation } from '@alfa-client/binary-file'; +import { isIncomingMail, ON_PAGE, PostfachMailLinkRel, PostfachMailResource } from '@alfa-client/postfach-shared'; import { StateResource } from '@alfa-client/tech-shared'; import { VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; import { Component, Inject, Input } from '@angular/core'; @@ -47,4 +43,7 @@ export class PostfachMailComponent { public get isIncomingMail(): boolean { return isIncomingMail(this.postfachMail); } + + protected readonly BinaryFileListOrientation = BinaryFileListOrientation; + protected readonly PostfachMailLinkRel = PostfachMailLinkRel; } diff --git a/alfa-client/libs/postfach/src/lib/postfach.module.ts b/alfa-client/libs/postfach/src/lib/postfach.module.ts index 2a6fe82af2922461a8ed345f322b54e754f2ccec..88fd93b41fff801fb308bfc6a227483cc5042fc4 100644 --- a/alfa-client/libs/postfach/src/lib/postfach.module.ts +++ b/alfa-client/libs/postfach/src/lib/postfach.module.ts @@ -24,17 +24,7 @@ import { BinaryFileModule } from '@alfa-client/binary-file'; import { ON_PAGE, PostfachSharedModule } from '@alfa-client/postfach-shared'; import { ConvertForDataTestPipe, FormatDateWithTimePipe, HasLinkPipe, ToEmbeddedResourcesPipe } from '@alfa-client/tech-shared'; -import { - BackButtonComponent, - CheckboxEnumEditorComponent, - IconButtonWithSpinnerComponent, - OzgcloudIconComponent, - OzgcloudStrokedButtonWithSpinnerComponent, - OzgcloudTextEditorComponent, - SpinnerComponent, - SubnavigationComponent, - TextAreaEditorComponent, -} from '@alfa-client/ui'; +import { BackButtonComponent, CheckboxEnumEditorComponent, IconButtonWithSpinnerComponent, OzgcloudIconComponent, OzgcloudStrokedButtonWithSpinnerComponent, OzgcloudTextEditorComponent, SpinnerComponent, SubnavigationComponent, TextAreaEditorComponent, } from '@alfa-client/ui'; import { UserProfileModule } from '@alfa-client/user-profile'; import { VorgangSharedUiModule } from '@alfa-client/vorgang-shared-ui'; import { CommonModule } from '@angular/common'; @@ -43,10 +33,10 @@ import { ReactiveFormsModule } from '@angular/forms'; import { MatIcon } from '@angular/material/icon'; import { RouterModule, Routes } from '@angular/router'; import { ButtonComponent, MailboxIconComponent, PlusIconComponent, TooltipDirective } from '@ods/system'; +import { MultiFileUploadComponent } from '../../../binary-file/src/lib/multi-file-upload/multi-file-upload.component'; import { PostfachMailButtonContainerComponent } from './postfach-mail-button-container/postfach-mail-button-container.component'; import { PostfachMailButtonComponent } from './postfach-mail-button-container/postfach-mail-button/postfach-mail-button.component'; import { PostfachMailFormComponent } from './postfach-mail-form/postfach-mail-form.component'; -import { PostfachNachrichtAttachmentContainerComponent } from './postfach-mail-form/postfach-nachricht-attachment-container/postfach-nachricht-attachment-container.component'; import { PostfachNachrichtReplyEditorContainerComponent } from './postfach-mail-form/postfach-nachricht-reply-editor-container/postfach-nachricht-reply-editor-container.component'; import { PostfachMailListContainerComponent } from './postfach-mail-list-container/postfach-mail-list-container.component'; import { PostfachMailListComponent } from './postfach-mail-list-container/postfach-mail-list/postfach-mail-list.component'; @@ -55,7 +45,6 @@ import { OutgoingMailErrorContainerComponent } from './postfach-mail-list-contai import { OutgoingMailErrorComponent } from './postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component'; import { OutgoingMailComponent } from './postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail.component'; import { PostfachNachrichtEditButtonContainerComponent } from './postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/postfach-nachricht-edit-button-container/postfach-nachricht-edit-button-container.component'; -import { PostfachMailAttachmentsComponent } from './postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-attachments/postfach-mail-attachments.component'; import { PostfachMailDateComponent } from './postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-date/postfach-mail-date.component'; import { PostfachMailComponent } from './postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail.component'; import { PostfachMailPdfButtonContainerComponent } from './postfach-mail-pdf-button-container/postfach-mail-pdf-button-container.component'; @@ -99,6 +88,7 @@ const routes: Routes = [ PlusIconComponent, MailboxIconComponent, TooltipDirective, + MultiFileUploadComponent, ], declarations: [ PostfachMailListContainerComponent, @@ -114,12 +104,10 @@ const routes: Routes = [ OutgoingMailErrorComponent, PostfachMailDateComponent, OutgoingMailErrorContainerComponent, - PostfachMailAttachmentsComponent, PostfachMailButtonComponent, PostfachMailPdfButtonContainerComponent, PostfachMailPdfButtonComponent, PostfachNachrichtEditButtonContainerComponent, - PostfachNachrichtAttachmentContainerComponent, PostfachNachrichtReplyEditorContainerComponent, ], exports: [PostfachMailListContainerComponent, PostfachMailButtonContainerComponent, PostfachMailFormComponent], diff --git a/alfa-client/libs/tech-shared/src/index.ts b/alfa-client/libs/tech-shared/src/index.ts index 6fbc4e5294b1490dd9924a225715b0007c0ba80f..be9ebae3ab1b5b0b13a375f8bd097acbfa8c11e8 100644 --- a/alfa-client/libs/tech-shared/src/index.ts +++ b/alfa-client/libs/tech-shared/src/index.ts @@ -57,6 +57,7 @@ export * from './lib/resource/resource.repository'; export * from './lib/resource/resource.rxjs.operator'; export * from './lib/resource/resource.service'; export * from './lib/resource/resource.util'; +export * from './lib/service/component.factory'; export * from './lib/service/formservice.abstract'; export * from './lib/tech.model'; export * from './lib/tech.util'; diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.spec.ts b/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.spec.ts index bfd30ddd0afb673c9ab9eb285d33643eace88139..5420e9992163adac652466364d736c6c50c78860 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.spec.ts @@ -25,7 +25,6 @@ import { getEmbeddedResource } from '@ngxp/rest'; import { DummyListLinkRel } from 'libs/tech-shared/test/dummy'; import { createDummyListResource, toResource } from 'libs/tech-shared/test/resource'; import { ListResource } from '../resource/resource.util'; -import { EMPTY_ARRAY } from '../tech.util'; import { ToEmbeddedResourcesPipe } from './to-embedded-resource.pipe'; describe('ToEmbeddedResourcesPipe', () => { @@ -47,19 +46,19 @@ describe('ToEmbeddedResourcesPipe', () => { it('should return an empty array on null as listResource', () => { const result: unknown[] = pipe.transform(null, DummyListLinkRel.LIST); - expect(result).toBe(EMPTY_ARRAY); + expect(result).toEqual([]); }); it('should return empty array on null as linkel', () => { const result: unknown[] = pipe.transform(listResource, null); - expect(result).toBe(EMPTY_ARRAY); + expect(result).toEqual([]); }); it('should return empty array non existing resources', () => { const result: unknown[] = pipe.transform(toResource({}), DummyListLinkRel.LIST); - expect(result).toBe(EMPTY_ARRAY); + expect(result).toEqual([]); }); }); }); diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.ts index dbb7025753327feff619bd1a0559951bf8b6ebe0..7b746907bf222789faf57e003583e46bcb5e16ed 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.ts @@ -26,7 +26,6 @@ import { Resource, getEmbeddedResource } from '@ngxp/rest'; import { isNil, isNull } from 'lodash-es'; import { LinkRelationName } from '../resource/resource.model'; import { ListResource } from '../resource/resource.util'; -import { EMPTY_ARRAY } from '../tech.util'; @Pipe({ name: 'toEmbeddedResources', @@ -34,8 +33,8 @@ import { EMPTY_ARRAY } from '../tech.util'; }) export class ToEmbeddedResourcesPipe implements PipeTransform { transform(listResource: ListResource, linkRel: LinkRelationName): Resource[] { - if (isNil(listResource) || isNil(linkRel)) return EMPTY_ARRAY; + if (isNil(listResource) || isNil(linkRel)) return []; const embeddedReources: Resource[] = getEmbeddedResource(listResource, linkRel); - return isNull(embeddedReources) ? EMPTY_ARRAY : embeddedReources; + return isNull(embeddedReources) ? [] : embeddedReources; } } diff --git a/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.spec.ts index 60c4640d92c10a70d0bec7d763d80a8f9618ff33..aa2dee9b6966288e6326463b82a4568abef68ecd 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.spec.ts @@ -30,7 +30,6 @@ import { DummyLinkRel, DummyListLinkRel } from 'libs/tech-shared/test/dummy'; import { createDummyListResource, createDummyResource, createFilledDummyListResource } from 'libs/tech-shared/test/resource'; import { BehaviorSubject, Observable, of } from 'rxjs'; import { multipleCold, singleCold, singleHot } from '../../../test/marbles'; -import { EMPTY_ARRAY } from '../tech.util'; import { ResourceListService } from './list-resource.service'; import { CreateResourceData, LinkRelationName, ListItemResource, ListResourceServiceConfig } from './resource.model'; import { ResourceRepository } from './resource.repository'; @@ -547,7 +546,7 @@ describe('ListResourceService', () => { service.getList = jest.fn().mockReturnValue(of(createEmptyStateResource())); service.getItems().subscribe((items) => { - expect(items).toEqual(EMPTY_ARRAY); + expect(items).toEqual([]); done(); }); }); diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts index 87869c5c4e0aa1dff4a3050b95ed26abd476c0d6..5bf83514f4fdbe127b04b8db9caf828408cdfdc1 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts @@ -82,12 +82,6 @@ describe('ResourceService', () => { isInvalidResourceCombinationSpy = jest.spyOn(ResourceUtil, 'isInvalidResourceCombination').mockReturnValue(true); }); - it('should have debounce', () => { - service.get().subscribe(); - - expect(service.handleResourceChanges).not.toHaveBeenCalled(); - }); - it('should handle config resource changed', fakeAsync(() => { service.get().subscribe(); tick(50); diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts index 1953e85ff4691dec3b95771a73f1bc1c7283efd1..b71624d87bffb0a0169167e60aba0c6be0fce2a1 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts @@ -24,35 +24,14 @@ import { HttpErrorResponse } from '@angular/common/http'; import { getUrl, hasLink, Resource, ResourceUri } from '@ngxp/rest'; import { isEqual, isNull } from 'lodash-es'; -import { - BehaviorSubject, - catchError, - combineLatest, - debounceTime, - filter, - first, - map, - Observable, - of, - startWith, - tap, - throwError, -} from 'rxjs'; +import { BehaviorSubject, catchError, combineLatest, filter, first, map, Observable, of, startWith, tap, throwError } from 'rxjs'; import { isUnprocessableEntity } from '../http.util'; import { HttpError } from '../tech.model'; import { isNotNull } from '../tech.util'; import { ResourceServiceConfig } from './resource.model'; import { ResourceRepository } from './resource.repository'; import { mapToFirst, mapToResource } from './resource.rxjs.operator'; -import { - createEmptyStateResource, - createErrorStateResource, - createStateResource, - isInvalidResourceCombination, - isLoadingRequired, - isStateResoureStable, - StateResource, -} from './resource.util'; +import { createEmptyStateResource, createErrorStateResource, createStateResource, isInvalidResourceCombination, isLoadingRequired, isStateResoureStable, StateResource, } from './resource.util'; /** * B = Type of baseresource @@ -70,7 +49,6 @@ export abstract class ResourceService<B extends Resource, T extends Resource> { public get(): Observable<StateResource<T>> { return combineLatest([this.stateResource.asObservable(), this.getConfigResource()]).pipe( - debounceTime(50), tap(([stateResource, configResource]) => this.handleResourceChanges(stateResource, configResource)), filter(([stateResource]) => !isInvalidResourceCombination(stateResource, this.configResource)), mapToFirst<T, B>(), diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.util.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.util.spec.ts index 3bfc293f96169bd19c2db03eb6c3030a15d8a8f1..5e5a9f2b37a30aeda80c61e09986d021e556cd41 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.util.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.util.spec.ts @@ -26,7 +26,6 @@ import { createCommandStateResource } from 'libs/command-shared/test/command'; import { DummyListLinkRel } from '../../../test/dummy'; import { createApiError } from '../../../test/error'; import { createDummyListResource, createDummyResource, toResource } from '../../../test/resource'; -import { EMPTY_ARRAY } from '../tech.util'; import { StateResource, containsLoading, @@ -139,13 +138,13 @@ describe('resource util', () => { it('should return empty array if state resource null', () => { const embedded = getEmbeddedResources(null, null); - expect(embedded).toEqual(EMPTY_ARRAY); + expect(embedded).toEqual([]); }); it('should return empty array if resource null', () => { const embedded = getEmbeddedResources(createEmptyStateResource(), null); - expect(embedded).toEqual(EMPTY_ARRAY); + expect(embedded).toEqual([]); }); it('should return null if embedded relation does not exist', () => { diff --git a/alfa-client/libs/tech-shared/src/lib/service/component.factory.spec.ts b/alfa-client/libs/tech-shared/src/lib/service/component.factory.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8a94aec3ede53c078234f2b4e75cde96aeedbdb --- /dev/null +++ b/alfa-client/libs/tech-shared/src/lib/service/component.factory.spec.ts @@ -0,0 +1,104 @@ +import { Mock, mock, mockGetValue } from '@alfa-client/test-utils'; +import { ApplicationRef, ComponentRef, EnvironmentInjector, Injector, Provider, ViewRef } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { OzgCloudComponentFactory } from './component.factory'; + +import { DIALOG_DATA } from '@angular/cdk/dialog'; +import { ComponentType } from '@angular/cdk/portal'; + +jest.mock('@angular/core', () => ({ + ...jest.requireActual('@angular/core'), + createComponent: jest.fn(), +})); + +describe('OzgCloudComponentFactory', () => { + let factory: OzgCloudComponentFactory; + + let appRef: Mock<ApplicationRef>; + let envInjector: Mock<EnvironmentInjector>; + + const dummyProvider: Provider = { provide: DIALOG_DATA, useValue: { someField: 'value' } } as Provider; + + beforeEach(() => { + appRef = mock(ApplicationRef); + envInjector = mock(EnvironmentInjector as any); + + factory = TestBed.inject(OzgCloudComponentFactory); + + mockGetValue(factory, 'appRef', appRef); + mockGetValue(factory, 'envInjector', envInjector); + }); + + it('should be created', () => { + expect(factory).toBeTruthy(); + }); + + describe('create component', () => { + let createComponentSpy: jest.SpyInstance; + const injectorMock: Mock<Injector> = mock(Injector as any); + const componentRefMock: Mock<ComponentRef<any>> = mock(ComponentRef as any); + + const componentType: ComponentType<any> = <any>{}; + const injector: Injector = <any>{}; + + beforeEach(() => { + createComponentSpy = jest.spyOn(require('@angular/core'), 'createComponent').mockReturnValue(componentRefMock); + factory._createElementInjector = jest.fn().mockReturnValue(injectorMock); + factory._registerComponentToChangeDetection = jest.fn(); + }); + + it('should call createComponent', () => { + factory.createComponent(componentType, injector, [dummyProvider]); + + expect(createComponentSpy).toHaveBeenCalledWith(componentType, { + environmentInjector: envInjector, + elementInjector: injectorMock, + }); + }); + + it('should register component to angulars change detection', () => { + factory.createComponent(componentType, injector); + + expect(factory._registerComponentToChangeDetection).toHaveBeenCalledWith(componentRefMock); + }); + + it('should return created component', () => { + const componentRef: ComponentRef<any> = factory.createComponent(componentType, injector); + + expect(componentRef).toBe(componentRefMock); + }); + }); + + describe('create element injector', () => { + const injectorMock: Mock<Injector> = mock(Injector as any); + + let createSpy: jest.SpyInstance; + + beforeEach(() => { + createSpy = jest.spyOn(Injector, 'create').mockReturnValue(injectorMock as Injector); + }); + + it('should create an injector by given parent selector', () => { + factory._createElementInjector(injectorMock, [dummyProvider]); + + expect(createSpy).toHaveBeenCalled(); + }); + + it('should return created injector', () => { + const injector: Injector = factory._createElementInjector(injectorMock); + + expect(injector).toBe(injectorMock); + }); + }); + + describe('register component to angulars change detection', () => { + const hostView: ViewRef = <any>{}; + const componentRef: ComponentRef<any> = <any>{ hostView }; + + it('should set hostview to appRef', () => { + factory._registerComponentToChangeDetection(componentRef); + + expect(appRef.attachView).toHaveBeenCalledWith(hostView); + }); + }); +}); diff --git a/alfa-client/libs/tech-shared/src/lib/service/component.factory.ts b/alfa-client/libs/tech-shared/src/lib/service/component.factory.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3d13c11b4897ff1204a3d565edfb18f7bee5b9a --- /dev/null +++ b/alfa-client/libs/tech-shared/src/lib/service/component.factory.ts @@ -0,0 +1,38 @@ +import { ComponentType } from '@angular/cdk/portal'; +import { + ApplicationRef, + ComponentRef, + createComponent, + EnvironmentInjector, + inject, + Injectable, + Injector, + Provider, +} from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class OzgCloudComponentFactory { + private readonly appRef = inject(ApplicationRef); + private readonly envInjector = inject(EnvironmentInjector); + + public createComponent<T>( + componentType: ComponentType<any>, + parentInjector: Injector, + providers: Provider[] = [], + ): ComponentRef<T> { + const component: ComponentRef<any> = <ComponentRef<any>>createComponent(componentType, { + environmentInjector: this.envInjector, + elementInjector: this._createElementInjector(parentInjector, providers), + }); + this._registerComponentToChangeDetection(component); + return component; + } + + _createElementInjector(parentInjector: Injector, providers: Provider[] = []): Injector { + return Injector.create({ providers, parent: parentInjector }); + } + + _registerComponentToChangeDetection(component: ComponentRef<any>): void { + this.appRef.attachView(component.hostView); + } +} diff --git a/alfa-client/libs/tech-shared/src/lib/tech.util.ts b/alfa-client/libs/tech-shared/src/lib/tech.util.ts index 01a8ec90cdc85a6ea0145415c50095c5366c79a6..baed9187b7bf29b1d6e18b958e3bdb4888b090bd 100644 --- a/alfa-client/libs/tech-shared/src/lib/tech.util.ts +++ b/alfa-client/libs/tech-shared/src/lib/tech.util.ts @@ -30,7 +30,6 @@ import { LinkRelationName } from './resource/resource.model'; import { ApiError } from './tech.model'; export const EMPTY_STRING: string = ''; -export const EMPTY_ARRAY = []; export function getBaseUrl(): string { const { protocol, host } = window.location; @@ -44,9 +43,7 @@ export function isEmptyObject(obj: any): boolean { export function replacePlaceholders(text: string, placeholders: { [key: string]: string }): string { let replaced: string = text; - Object.keys(placeholders).forEach( - (key: string) => (replaced = replacePlaceholder(replaced, key, placeholders[key])), - ); + Object.keys(placeholders).forEach((key: string) => (replaced = replacePlaceholder(replaced, key, placeholders[key]))); return replaced; } @@ -56,9 +53,7 @@ export function replacePlaceholder(text: string, placeholder: string, value: str } export function hasExceptionId(apiError: ApiError): boolean { - return ( - isNotNil(apiError) && isNotNil(apiError.issues) && isNotNil(apiError.issues[0].exceptionId) - ); + return isNotNil(apiError) && isNotNil(apiError.issues) && isNotNil(apiError.issues[0].exceptionId); } export function sleep(delayInMs: number): void { @@ -152,9 +147,5 @@ export function notHasAnyLink(resource: Resource, ...links: LinkRelationName[]): } export function hasAnyLink(resource: Resource, ...links: LinkRelationName[]): boolean { - return !isEmpty( - links - .map((link: LinkRelationName) => hasLink(resource, link)) - .filter((hasLink: boolean) => hasLink === true), - ); + return !isEmpty(links.map((link: LinkRelationName) => hasLink(resource, link)).filter((hasLink: boolean) => hasLink === true)); } diff --git a/alfa-client/libs/tech-shared/test/data-test.ts b/alfa-client/libs/tech-shared/test/data-test.ts index 795b2dee30c083f71da5842c2cc64738c3dd9da4..c4fbe01458d666825688364aee1f75b9fac3ea4e 100644 --- a/alfa-client/libs/tech-shared/test/data-test.ts +++ b/alfa-client/libs/tech-shared/test/data-test.ts @@ -35,10 +35,9 @@ export function getDataTestIdOf(value: string): string { return `[data-test-id="${value}"]`; } +/** + * @deprecated use getDataTestIfOf instead and a direct data-test-id at the component + */ export function getDataTestIdAttributeOf(value: string): string { return `[dataTestId="${value}"]`; } - -export function getDynamicDataTestIdAttributOf(value: string): string { - return `[ng-reflect-data-test-id="${value}"]`; -} diff --git a/alfa-client/libs/tech-shared/test/file.ts b/alfa-client/libs/tech-shared/test/file.ts index 5feadc2fcd75f4e7b9144afa4c53194e995b43d3..5f4a0578aa673642049ba6ed8e54f5eb99f39222 100644 --- a/alfa-client/libs/tech-shared/test/file.ts +++ b/alfa-client/libs/tech-shared/test/file.ts @@ -26,3 +26,12 @@ import { faker } from '@faker-js/faker'; export function createFile(): File { return <any>{ name: faker.string.sample(10), type: 'image/png', size: 512 }; } + +export function createFileList(): FileList { + const file = createFile(); + return { + 0: file, + length: 2, + item: (index: number) => file, + }; +} diff --git a/alfa-client/libs/tech-shared/test/http.ts b/alfa-client/libs/tech-shared/test/http.ts index 76abc5588ba27b70f755cc07437150add45375c7..0f03449f6400f456d027b4b2fa524b101b337155 100644 --- a/alfa-client/libs/tech-shared/test/http.ts +++ b/alfa-client/libs/tech-shared/test/http.ts @@ -21,7 +21,8 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; +import { HttpHeader } from '@alfa-client/tech-shared'; +import { HttpErrorResponse, HttpResponse, HttpStatusCode } from '@angular/common/http'; import { createApiError } from './error'; export function createHttpErrorResponse(): HttpErrorResponse { @@ -30,3 +31,16 @@ export function createHttpErrorResponse(): HttpErrorResponse { status: HttpStatusCode.ServiceUnavailable, }; } + +export function createHttpResponse(): HttpResponse<Object> { + return { + status: HttpStatusCode.Accepted, + body: null, + clone: null, + headers: <any>{ get: (headerName: HttpHeader) => 'headerDummyUrl' }, + ok: null, + statusText: null, + type: null, + url: null, + }; +} diff --git a/alfa-client/libs/tech-shared/test/marbles.ts b/alfa-client/libs/tech-shared/test/marbles.ts index fecda8769f38a80dd34c693c9ca39bf175a69eef..c84c4c3f8b35246ac3782d714d993be1bd36d9a8 100644 --- a/alfa-client/libs/tech-shared/test/marbles.ts +++ b/alfa-client/libs/tech-shared/test/marbles.ts @@ -21,7 +21,8 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ObservableWithSubscriptions, cold, hot } from 'jest-marbles'; +import { cold, hot, ObservableWithSubscriptions } from 'jest-marbles'; +import { HttpError } from '../src/lib/tech.model'; export function singleHot(object: any, frame: string = 'a'): ObservableWithSubscriptions { return hot(frame, { a: object }); @@ -38,3 +39,11 @@ export function singleColdCompleted(object: any, frame: string = 'a'): Observabl export function multipleCold(first: any, second: any): ObservableWithSubscriptions { return cold('ab', { a: first, b: second }); } + +export function coldError(error: HttpError): ObservableWithSubscriptions { + return cold('-#', null, { error: { error: error } }); +} + +export function coldStartWithError(startWith: any, error: any): ObservableWithSubscriptions { + return cold('a(b|)', { a: startWith, b: error }); +} diff --git a/alfa-client/libs/test-utils/src/lib/dialog.ts b/alfa-client/libs/test-utils/src/lib/dialog.ts index 7e09b7bea501d2f2d52bd1b696a7f27d975efb4f..5a8c5fe4737efc92501377610d333e50af98f5d8 100644 --- a/alfa-client/libs/test-utils/src/lib/dialog.ts +++ b/alfa-client/libs/test-utils/src/lib/dialog.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { jest } from '@jest/globals'; -import { EMPTY, Observable } from 'rxjs'; +import { EMPTY, Observable, of } from 'rxjs'; export class DialogRefMock<R = unknown> { public keydownEvents: Observable<KeyboardEvent> = EMPTY; @@ -34,3 +34,9 @@ export class DialogRefMock<R = unknown> { export function createDialogRefMock<R = unknown>(): DialogRefMock<R> { return new DialogRefMock(); } + +export function createdClosedDialogRefMock<R = unknown>(result?: R): DialogRefMock<R> { + const dialogRefMock = createDialogRefMock<R>(); + dialogRefMock.closed = of(result); + return dialogRefMock; +} diff --git a/alfa-client/libs/test-utils/src/lib/helper.ts b/alfa-client/libs/test-utils/src/lib/helper.ts index e847268dea4139a32915ce071ddd79fb20da4e39..66f33c3579e18d88743d3b23ea96703a2799bb04 100644 --- a/alfa-client/libs/test-utils/src/lib/helper.ts +++ b/alfa-client/libs/test-utils/src/lib/helper.ts @@ -55,6 +55,11 @@ export function dispatchEventFromFixture<T>(fixture: ComponentFixture<T>, elemen element.nativeElement.dispatchEvent(new Event(event)); } +export function dispatchEventFromFixtureByType<T, C>(fixture: ComponentFixture<T>, component: Type<C>, event: string): void { + const element: DebugElement = getDebugElementFromFixtureByType(fixture, component); + element.nativeElement.dispatchEvent(new Event(event)); +} + export function triggerEvent<T>(eventData: EventData<T>) { const element: DebugElement = getDebugElementFromFixtureByCss(eventData.fixture, eventData.elementSelector); element.triggerEventHandler(eventData.name, eventData.data); diff --git a/alfa-client/libs/test-utils/src/lib/mocking.ts b/alfa-client/libs/test-utils/src/lib/mocking.ts index 0fb616db60420994880e9acfddec1d0d37e10486..d42ae2de44f2a37bec9daeed0388f20812b04364 100644 --- a/alfa-client/libs/test-utils/src/lib/mocking.ts +++ b/alfa-client/libs/test-utils/src/lib/mocking.ts @@ -53,3 +53,13 @@ export function mockGetValue(object: any, name: string, returnValue: any): void get: jest.fn(() => returnValue), }); } + +export function mockWindowError(): any { + const errorHandler = jest.fn(); + window.onerror = errorHandler; + return errorHandler; +} + +export function assignValue<T>(object: any, attributeName: string, toAssignValue: any): T { + return Object.assign(object, { [attributeName]: toAssignValue }); +} diff --git a/alfa-client/libs/test-utils/src/lib/model.ts b/alfa-client/libs/test-utils/src/lib/model.ts index 1094ac96376b5a8d2d81cf5a7ebf13c69aa2a550..b2564a9ddf9869f6511f01c27ac61a0de6943879 100644 --- a/alfa-client/libs/test-utils/src/lib/model.ts +++ b/alfa-client/libs/test-utils/src/lib/model.ts @@ -30,6 +30,6 @@ export interface EventData<T> { data?: unknown; } -export const MockEvent = { - CLICK: 'clickEmitter', -}; +export enum MockEvent { + CLICK = 'clickEmitter', +} diff --git a/alfa-client/libs/ui/src/index.ts b/alfa-client/libs/ui/src/index.ts index 2db371a18e23b0b2d01eb93bcf1738bd8ef43f61..76832535931b3342403ac5674f6f866c4c7eb58f 100644 --- a/alfa-client/libs/ui/src/index.ts +++ b/alfa-client/libs/ui/src/index.ts @@ -47,6 +47,7 @@ export * from './lib/ui/open-url-button/open-url-button.component'; export * from './lib/ui/ozgcloud-button/ozgcloud-button-with-spinner/ozgcloud-button-with-spinner.component'; export * from './lib/ui/ozgcloud-button/ozgcloud-icon-button-primary/ozgcloud-icon-button-primary.component'; export * from './lib/ui/ozgcloud-button/ozgcloud-stroked-button-with-spinner/ozgcloud-stroked-button-with-spinner.component'; +export * from './lib/ui/ozgcloud-dialog/ozgcloud-dialog.model'; export * from './lib/ui/ozgcloud-dialog/ozgcloud-dialog.result'; export * from './lib/ui/ozgcloud-dialog/ozgcloud-dialog.service'; export * from './lib/ui/ozgcloud-icon/ozgcloud-icon.component'; diff --git a/alfa-client/libs/ui/src/lib/ui/back-button/back-button.component.ts b/alfa-client/libs/ui/src/lib/ui/back-button/back-button.component.ts index a5c00893b3fa0fe9c3745afa4a750f395f06df70..db593f08cb17ab00615fb0314f68d044491e9665 100644 --- a/alfa-client/libs/ui/src/lib/ui/back-button/back-button.component.ts +++ b/alfa-client/libs/ui/src/lib/ui/back-button/back-button.component.ts @@ -22,9 +22,6 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { Component, Input } from '@angular/core'; -import { MatIconAnchor } from '@angular/material/button'; -import { MatIcon } from '@angular/material/icon'; -import { MatTooltip } from '@angular/material/tooltip'; import { RouterLink } from '@angular/router'; import { ArrowBackIconComponent, ButtonComponent, TooltipDirective } from '@ods/system'; @@ -33,7 +30,7 @@ import { ArrowBackIconComponent, ButtonComponent, TooltipDirective } from '@ods/ templateUrl: './back-button.component.html', styleUrls: ['./back-button.component.scss'], standalone: true, - imports: [MatIconAnchor, RouterLink, MatTooltip, MatIcon, TooltipDirective, ButtonComponent, ArrowBackIconComponent], + imports: [RouterLink, TooltipDirective, ButtonComponent, ArrowBackIconComponent], }) export class BackButtonComponent { @Input() label: string; diff --git a/alfa-client/libs/ui/src/lib/ui/dialog/dialog.service.ts b/alfa-client/libs/ui/src/lib/ui/dialog/dialog.service.ts index cd72feef7dac8d6194e6fb83ab08e9b76e38ffa7..48e0bf00d2f803bbe0d4cdcd95c0ff0e84479aa1 100644 --- a/alfa-client/libs/ui/src/lib/ui/dialog/dialog.service.ts +++ b/alfa-client/libs/ui/src/lib/ui/dialog/dialog.service.ts @@ -30,6 +30,10 @@ import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dial import { InternalServerErrorDialogComponent } from '../notification/internal-server-error-dialog/internal-server-error-dialog.component'; import { RetryInTimeDialog } from './retry-in-time.dialog'; +/** + * @deprecated use {@link OzgcloudDialogService} instead + * @see OzgcloudDialogService + */ @Injectable({ providedIn: 'root' }) export class DialogService { private dialog = inject(MatDialog); diff --git a/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html index 33cd2c515616d0786b8c14d6a39948451590524c..3a5337cbab58d332a3c3e4e8d500db3f33bd1d2d 100644 --- a/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html +++ b/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html @@ -23,7 +23,7 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<mat-form-field> +<mat-form-field class="max-w-48"> <mat-label>{{ label }}</mat-label> <input matInput diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.model.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..441d097a057ac2121ffd01610740f149e6d6d5b1 --- /dev/null +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.model.ts @@ -0,0 +1,4 @@ +import { ComponentType } from '@angular/cdk/portal'; +import { InjectionToken } from '@angular/core'; + +export const DIALOG_COMPONENT: InjectionToken<ComponentType<any>> = new InjectionToken<ComponentType<any>>('DialogComponent'); diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.spec.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.spec.ts index 91585f64a670993693cd3eb2202a181dc9d805ae..b95396349c8cb79f0e97601877a7fcd108529e56 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.spec.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { Mock, mock } from '@alfa-client/test-utils'; -import { Dialog, DialogConfig } from '@angular/cdk/dialog'; +import { Dialog, DialogConfig, DialogRef } from '@angular/cdk/dialog'; import { TestBed } from '@angular/core/testing'; import { OzgcloudDialogService } from './ozgcloud-dialog.service'; @@ -82,6 +82,26 @@ describe('OzgcloudDialogService', () => { }); }); + describe('open in context', () => { + const dialogRefMock: Mock<DialogRef> = mock(DialogRef); + + beforeEach(() => { + service.openInCallingComponentContext = jest.fn().mockReturnValue(dialogRefMock); + }); + + it('should call open in calling component context', () => { + service.openInContext(component, viewContainerRef, dialogData); + + expect(service.openInCallingComponentContext).toHaveBeenCalledWith(component, viewContainerRef, dialogData); + }); + + it('should return dialog ref', () => { + const dialogRef: DialogRef = service.openInContext(component, viewContainerRef, dialogData); + + expect(dialogRef).toBe(dialogRefMock); + }); + }); + describe('openInCallingComponentContext', () => { it('should open dialog with data', () => { service.openInCallingComponentContext(component, viewContainerRef, dialogData); diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.ts index 077780a3e1d01c0e198efc1de9f12244315b1194..aebed88efe5a54fafce9ec09283f0314c744fc5e 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.ts +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.ts @@ -30,7 +30,7 @@ import { isNil } from 'lodash-es'; providedIn: 'root', }) export class OzgcloudDialogService { - private dialog = inject(Dialog); + private readonly dialog = inject(Dialog); readonly WIZARD_DIALOG_CONFIG: DialogConfig = { width: '1000px', @@ -47,10 +47,21 @@ export class OzgcloudDialogService { return this.openDialog<C, R>(component, this.buildDialogConfigWithData<D>(data, this.WIZARD_DIALOG_CONFIG)); } + /** + * @deprecated use openInContext instead + */ public open<C, D = unknown, R = unknown>(component: ComponentType<C>, data?: D): DialogRef<R> { return this.openDialog(component, this.buildDialogConfigWithData(data)); } + public openInContext<C, D = unknown, R = unknown>( + component: ComponentType<C>, + viewContainerRef: ViewContainerRef, + data?: D, + ): DialogRef<R> { + return this.openInCallingComponentContext<C, D, R>(component, viewContainerRef, data); + } + public openFullScreenDialog<C, D = unknown, R = unknown>( component: ComponentType<C>, viewContainerRef: ViewContainerRef, @@ -59,6 +70,7 @@ export class OzgcloudDialogService { return this.openInCallingComponentContext<C, D, R>(component, viewContainerRef, data, this.GREY_BLUR_CONFIG); } + //TODO private machen und openInContext bei den jeweiligen Stellen nutzen public openInCallingComponentContext<C, D = unknown, R = unknown>( component: ComponentType<C>, viewContainerRef: ViewContainerRef, diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.html b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.html index 9ec99cebdafed843b5ded6af4b56c34fb2b04638..f0b1defde6fba19d8ef6144a37945c05dcbc3735 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.html +++ b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.html @@ -23,11 +23,6 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<ods-dropdown-menu-text-item - class="border-b border-b-grayborder border-t-grayborder bg-whitetext" - title="Benutzerleitfaden" - description="Alle Funktionen der Allgemeinen Fachanwendung (Alfa) erklärt." -> - <ods-file-icon icon fileType="pdf" size="large"></ods-file-icon> - <alfa-open-documentation-button additionalContent [url]="url" data-test-id="documentations-component" /> -</ods-dropdown-menu-text-item> +<ods-dropdown-menu-link-item caption="Benutzerleitfaden Alfa" text="PDF öffnen" [url]="url"> + <ods-file-icon icon fileType="pdf" size="medium" /> +</ods-dropdown-menu-link-item> diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.spec.ts b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.spec.ts index b7307c4a9f800117718ab72571c87289e011907c..fb232797a308c689f244460f103deafbabf9c5f3 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.spec.ts +++ b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/documentation.component.spec.ts @@ -22,10 +22,9 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { DropdownMenuTextItemComponent, FileIconComponent } from '@ods/system'; +import { DropdownMenuLinkItemComponent, FileIconComponent } from '@ods/system'; import { MockComponent } from 'ng-mocks'; import { DocumentationComponent } from './documentation.component'; -import { OpenDocumentationButtonComponent } from './open-documentation-button/open-documentation-button.component'; describe('DocumentationComponent', () => { let component: DocumentationComponent; @@ -33,12 +32,7 @@ describe('DocumentationComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - DocumentationComponent, - MockComponent(OpenDocumentationButtonComponent), - MockComponent(DropdownMenuTextItemComponent), - MockComponent(FileIconComponent), - ], + declarations: [DocumentationComponent, MockComponent(DropdownMenuLinkItemComponent), MockComponent(FileIconComponent)], }).compileComponents(); fixture = TestBed.createComponent(DocumentationComponent); diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.html b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.html deleted file mode 100644 index d3f460b90b5c7c7232d01e2b4e0674ddee2a8a8c..0000000000000000000000000000000000000000 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.html +++ /dev/null @@ -1,32 +0,0 @@ -<!-- - - Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<ozgcloud-open-url-button - text="Öffnen" - [url]="url" - [targetName]="'_blank'" - [tooltip]="'Öffnet in einem neuen Tab'" - data-test-id="open-documentation-button" -/> diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.spec.ts b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.spec.ts deleted file mode 100644 index a57801c4f80be45225f6d78c3789d1cdec31b271..0000000000000000000000000000000000000000 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { OpenDocumentationButtonComponent } from './open-documentation-button.component'; -import { MockComponent } from 'ng-mocks'; -import { OpenUrlButtonComponent } from '@alfa-client/ui'; - -describe('OpenDocumentationButtonComponent', () => { - let component: OpenDocumentationButtonComponent; - let fixture: ComponentFixture<OpenDocumentationButtonComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [OpenDocumentationButtonComponent, MockComponent(OpenUrlButtonComponent)], - }).compileComponents(); - - fixture = TestBed.createComponent(OpenDocumentationButtonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.ts b/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.ts deleted file mode 100644 index c0719256cfb74979b4cbe95a7c3e3c451cd08af4..0000000000000000000000000000000000000000 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/documentation/open-documentation-button/open-documentation-button.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { Component, Input } from '@angular/core'; - -@Component({ - selector: 'alfa-open-documentation-button', - templateUrl: './open-documentation-button.component.html', - styleUrls: ['./open-documentation-button.component.scss'], -}) -export class OpenDocumentationButtonComponent { - @Input() url: string; -} diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html index 24ee1e914732e11ea00e3dad00a7cffea735b000..24583fe97f504dcb3d35279e5a1ab0d932a97ca9 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html +++ b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html @@ -38,6 +38,7 @@ } @if (apiRootStateResource?.resource?.impressumUrl) { + <div class="h-2"></div> <ods-dropdown-menu-link-item [url]="apiRootStateResource.resource.impressumUrl" text="Impressum" data-test-id="impressum" /> } </ods-dropdown-menu> diff --git a/alfa-client/libs/user-assistance/src/lib/user-assistance.module.ts b/alfa-client/libs/user-assistance/src/lib/user-assistance.module.ts index 63fc01ce61325ce4935a8292df7314a550e83940..f448be3a653b757112bf18d262ff617fcf3600ab 100644 --- a/alfa-client/libs/user-assistance/src/lib/user-assistance.module.ts +++ b/alfa-client/libs/user-assistance/src/lib/user-assistance.module.ts @@ -34,7 +34,6 @@ import { import { MatFabButton } from '@angular/material/button'; import { MatMenuTrigger } from '@angular/material/menu'; import { DocumentationComponent } from './help-menu/documentation/documentation.component'; -import { OpenDocumentationButtonComponent } from './help-menu/documentation/open-documentation-button/open-documentation-button.component'; import { HelpButtonComponent } from './help-menu/help-button/help-button.component'; import { HelpMenuComponent } from './help-menu/help-menu.component'; import { OpenUrlButtonComponent } from '@alfa-client/ui'; @@ -53,7 +52,7 @@ import { OpenUrlButtonComponent } from '@alfa-client/ui'; DropdownMenuLinkItemComponent, OpenUrlButtonComponent, ], - declarations: [HelpMenuComponent, DocumentationComponent, OpenDocumentationButtonComponent, HelpButtonComponent], + declarations: [HelpMenuComponent, DocumentationComponent, HelpButtonComponent], exports: [HelpMenuComponent], }) export class UserAssistanceModule {} diff --git a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html index a2aac078aaf9d5fc6783864da5b363cf460d9bee..797bf6c78e5323f1914b56376d75b5409c19b3c5 100644 --- a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html +++ b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html @@ -36,7 +36,7 @@ class="user-profile-icon" > </alfa-user-icon> - <ods-dropdown-menu-button-item caption="Abmelden" (itemClicked)="logoutEmitter.emit()" dataTestId="logout-button"> - <ods-logout-icon icon /> + <ods-dropdown-menu-button-item caption="Abmelden" (clickEmitter)="logoutEmitter.emit()" dataTestId="logout-button"> + <ods-logout-icon icon class="fill-primary" /> </ods-dropdown-menu-button-item> </ods-dropdown-menu> diff --git a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.spec.ts b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.spec.ts index ab53c008163ee44d5f785b065da91de6f203363c..2bf68369cdab87155caf1bb0d47489feb1552fc7 100644 --- a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.spec.ts +++ b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.spec.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { createStateResource } from '@alfa-client/tech-shared'; -import { dispatchEventFromFixture, getElementComponentFromFixtureByCss, mock, useFromMock } from '@alfa-client/test-utils'; +import { dispatchEventFromFixture, getElementComponentFromFixtureByCss, mock, MockEvent, useFromMock, } from '@alfa-client/test-utils'; import { UserIconComponent } from '@alfa-client/user-profile'; import { getUserName, UserProfileResource } from '@alfa-client/user-profile-shared'; import { EventEmitter } from '@angular/core'; @@ -90,7 +90,7 @@ describe('UserProfileInHeaderComponent', () => { describe('template', () => { describe('click on logout button', () => { it('should emit logout event', () => { - dispatchEventFromFixture(fixture, logoutButton, 'itemClicked'); + dispatchEventFromFixture(fixture, logoutButton, MockEvent.CLICK); expect(component.logoutEmitter.emit).toHaveBeenCalled(); }); diff --git a/alfa-client/libs/vorgang-detail/src/lib/buttons/bescheiden-button/bescheiden-button.component.spec.ts b/alfa-client/libs/vorgang-detail/src/lib/buttons/bescheiden-button/bescheiden-button.component.spec.ts index 072f568bac0f7d05aac47ee54e1e084025d3bd01..932363f2b33c3e0ebf50e8082a9389fd83a0a5a7 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/buttons/bescheiden-button/bescheiden-button.component.spec.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/buttons/bescheiden-button/bescheiden-button.component.spec.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BescheidWizardContainerComponent } from '@alfa-client/bescheid'; -import { BescheidResource, BescheidService, BescheidWizardDialogResult } from '@alfa-client/bescheid-shared'; +import { BescheidWizardDialogResult } from '@alfa-client/bescheid-shared'; import { CommandResource } from '@alfa-client/command-shared'; import { createEmptyStateResource, createStateResource, HasLinkPipe, StateResource } from '@alfa-client/tech-shared'; import { @@ -37,13 +37,7 @@ import { triggerEvent, } from '@alfa-client/test-utils'; import { OzgcloudDialogService } from '@alfa-client/ui'; -import { BescheidenDialogData } from '@alfa-client/vorgang-detail'; -import { - VorgangCommandService, - VorgangService, - VorgangWithEingangLinkRel, - VorgangWithEingangResource, -} from '@alfa-client/vorgang-shared'; +import { VorgangCommandService, VorgangService, VorgangWithEingangLinkRel } from '@alfa-client/vorgang-shared'; import { DialogRef } from '@angular/cdk/dialog'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ButtonWithSpinnerComponent } from '@ods/component'; @@ -53,7 +47,6 @@ import { getDataTestIdAttributeOf } from 'libs/tech-shared/test/data-test'; import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; import { MockComponent } from 'ng-mocks'; import { Observable, of } from 'rxjs'; -import { createBescheidResource } from '../../../../../bescheid-shared/src/test/bescheid'; import { VorgangDetailBescheidenComponent } from '../../vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden.component'; import { BescheidenButtonComponent } from './bescheiden-button.component'; @@ -66,7 +59,6 @@ describe('BescheidenButtonComponent', () => { let vorgangCommandService: Mock<VorgangCommandService>; let ozgcloudDialogService: Mock<OzgcloudDialogService>; - let bescheidService: Mock<BescheidService>; let vorgangService: Mock<VorgangService>; const dialogRef = <DialogRef<VorgangDetailBescheidenComponent>>{}; @@ -74,7 +66,6 @@ describe('BescheidenButtonComponent', () => { beforeEach(() => { vorgangCommandService = mock(VorgangCommandService); ozgcloudDialogService = mock(OzgcloudDialogService); - bescheidService = mock(BescheidService); vorgangService = mock(VorgangService); }); @@ -96,10 +87,6 @@ describe('BescheidenButtonComponent', () => { provide: OzgcloudDialogService, useValue: ozgcloudDialogService, }, - { - provide: BescheidService, - useValue: bescheidService, - }, { provide: VorgangService, useValue: vorgangService, @@ -225,84 +212,6 @@ describe('BescheidenButtonComponent', () => { }); }); - describe('openBescheidDialogWithNewDraft', () => { - it('should open wizard', () => { - component.vorgang = createVorgangWithEingangResource(); - component.openDialog = jest.fn(); - - component.openBescheidDialogWithNewDraft(); - - expect(component.openDialog).toHaveBeenCalledWith({ - vorgangWithEingangResource: component.vorgang, - bescheidDraftResource: null, - }); - }); - }); - - describe('openBescheidenDialogWithExistingDraft', () => { - const bescheidDraftResource: BescheidResource = createBescheidResource(); - const bescheidDraftStateResource: StateResource<BescheidResource> = createStateResource(bescheidDraftResource); - const vorgangWithEingangResource: VorgangWithEingangResource = createVorgangWithEingangResource([ - VorgangWithEingangLinkRel.BESCHEID_DRAFT, - ]); - - beforeEach(() => { - component.vorgang = vorgangWithEingangResource; - bescheidService.getBescheidDraftIfExists.mockReturnValue(of(bescheidDraftStateResource)); - }); - - it('should open wizard if bescheid draft loaded', () => { - component.openBescheidenDialogWithExistingDraft(); - - expect(ozgcloudDialogService.openWizard).toHaveBeenCalledWith(VorgangDetailBescheidenComponent, { - bescheidDraftResource, - vorgangWithEingangResource, - }); - }); - - it('should not open wizard if bescheid draft not loaded', () => { - bescheidService.getBescheidDraftIfExists.mockReturnValue(of(createEmptyStateResource())); - - component.openBescheidenDialogWithExistingDraft(); - - expect(ozgcloudDialogService.openWizard).not.toHaveBeenCalled(); - }); - - it('should not open wizard on loading bescheid draft', () => { - bescheidService.getBescheidDraftIfExists.mockReturnValue( - of({ ...createStateResource(createBescheidResource()), loading: true }), - ); - - component.openBescheidenDialogWithExistingDraft(); - - expect(ozgcloudDialogService.openWizard).not.toHaveBeenCalled(); - }); - }); - - describe('openDialog', () => { - const bescheidDraftResource: BescheidResource = createBescheidResource(); - const bescheidDraftStateResource: StateResource<BescheidResource> = createStateResource(bescheidDraftResource); - const vorgangWithEingangResource: VorgangWithEingangResource = createVorgangWithEingangResource([ - VorgangWithEingangLinkRel.BESCHEID_DRAFT, - ]); - - const dialogData: BescheidenDialogData = { - bescheidDraftResource: bescheidDraftResource, - vorgangWithEingangResource: vorgangWithEingangResource, - }; - - beforeEach(() => { - component.vorgang = vorgangWithEingangResource; - bescheidService.getBescheidDraftIfExists.mockReturnValue(of(bescheidDraftStateResource)); - }); - - it('should call ozgcloudDialogService.openWizard', () => { - component.openDialog(dialogData); - - expect(ozgcloudDialogService.openWizard).toHaveBeenCalledWith(VorgangDetailBescheidenComponent, dialogData); - }); - }); - describe('handleBescheidWizardClosed', () => { it('should reload current vorgang', () => { component.handleBescheidWizardClosed({ reloadVorgang: true }); diff --git a/alfa-client/libs/vorgang-detail/src/lib/buttons/bescheiden-button/bescheiden-button.component.ts b/alfa-client/libs/vorgang-detail/src/lib/buttons/bescheiden-button/bescheiden-button.component.ts index 65e3c32d26e50c662463f04804b56def5f01dfca..14105a18936391dbbf8646abed381e2b9273767c 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/buttons/bescheiden-button/bescheiden-button.component.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/buttons/bescheiden-button/bescheiden-button.component.ts @@ -22,17 +22,21 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BescheidWizardContainerComponent } from '@alfa-client/bescheid'; -import { BescheidResource, BescheidService, BescheidWizardDialogResult } from '@alfa-client/bescheid-shared'; +import { BescheidWizardDialogResult } from '@alfa-client/bescheid-shared'; import { CommandResource } from '@alfa-client/command-shared'; -import { createEmptyStateResource, isLoaded, isNotNil, StateResource } from '@alfa-client/tech-shared'; +import { createEmptyStateResource, isNotNil, StateResource } from '@alfa-client/tech-shared'; import { OzgcloudDialogService } from '@alfa-client/ui'; -import { VorgangCommandService, VorgangService, VorgangWithEingangLinkRel, VorgangWithEingangResource, } from '@alfa-client/vorgang-shared'; +import { + VorgangCommandService, + VorgangService, + VorgangWithEingangLinkRel, + VorgangWithEingangResource, +} from '@alfa-client/vorgang-shared'; import { DialogRef } from '@angular/cdk/dialog'; import { Component, Input, OnInit } from '@angular/core'; import { hasLink } from '@ngxp/rest'; -import { filter, first, map, Observable, of } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { BescheidenDialogData } from '../../vorgang-detail-page/vorgang-detail-bescheiden/bescheiden.model'; -import { VorgangDetailBescheidenComponent } from '../../vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden.component'; @Component({ selector: 'alfa-bescheiden-button', @@ -61,7 +65,6 @@ export class BescheidenButtonComponent implements OnInit { private vorgangCommandService: VorgangCommandService, private vorgangService: VorgangService, private ozgcloudDialogService: OzgcloudDialogService, - private bescheidService: BescheidService, ) {} ngOnInit(): void { @@ -98,38 +101,4 @@ export class BescheidenButtonComponent implements OnInit { this.vorgangService.reloadCurrentVorgang(); } } - - openBescheidenDialogWithExistingDraft(): void { - this.bescheidService - .getBescheidDraftIfExists() - .pipe( - filter(isLoaded), - first(), - map((stateResource: StateResource<BescheidResource>) => stateResource.resource), - ) - .subscribe((bescheidDraftResource: BescheidResource) => { - const dialogData: BescheidenDialogData = { - bescheidDraftResource, - vorgangWithEingangResource: this.vorgang, - }; - - this.openDialog(dialogData); - }); - } - - openBescheidDialogWithNewDraft(): void { - const dialogData: BescheidenDialogData = { - bescheidDraftResource: null, - vorgangWithEingangResource: this.vorgang, - }; - - this.openDialog(dialogData); - } - - openDialog(dialogData: BescheidenDialogData): void { - this.ozgcloudDialogService.openWizard<VorgangDetailBescheidenComponent, BescheidenDialogData>( - VorgangDetailBescheidenComponent, - dialogData, - ); - } } diff --git a/alfa-client/libs/vorgang-shared/src/lib/+state/vorgang.reducer.spec.ts b/alfa-client/libs/vorgang-shared/src/lib/+state/vorgang.reducer.spec.ts index 49ef271f9dfe7e93af9a114ad8840972db003a6f..c3358acb510534afa4df0bca712d8b2ff8161bb5 100644 --- a/alfa-client/libs/vorgang-shared/src/lib/+state/vorgang.reducer.spec.ts +++ b/alfa-client/libs/vorgang-shared/src/lib/+state/vorgang.reducer.spec.ts @@ -23,17 +23,11 @@ */ import { ApiRootResource } from '@alfa-client/api-root-shared'; import { BinaryFileListResource } from '@alfa-client/binary-file-shared'; -import { - CommandListResource, - CommandOrder, - CommandResource, - CreateCommand, -} from '@alfa-client/command-shared'; +import { CommandListResource, CommandOrder, CommandResource, CreateCommand } from '@alfa-client/command-shared'; import { RouteData } from '@alfa-client/navigation-shared'; import { ApiError, ApiErrorAction, - EMPTY_ARRAY, EMPTY_STRING, StateResource, createEmptyStateResource, @@ -45,11 +39,7 @@ import { Action } from '@ngrx/store'; import { Resource, ResourceUri } from '@ngxp/rest'; import { createApiRootResource } from 'libs/api-root-shared/test/api-root'; import { createBinaryFileListResource } from 'libs/binary-file-shared/test/binary-file'; -import { - createCommand, - createCommandListResource, - createCommandResource, -} from 'libs/command-shared/test/command'; +import { createCommand, createCommandListResource, createCommandResource } from 'libs/command-shared/test/command'; import { createRouteData } from 'libs/navigation-shared/test/navigation-test-factory'; import { createDummyResource } from 'libs/tech-shared/test/resource'; import { @@ -129,9 +119,7 @@ describe('Vorgang Reducer', () => { ...createVorgangListResourceWithResource(vorgaenge, [VorgangListLinkRel.NEXT]), statistic, }; - const action: VorgangListAction & Action<string> = VorgangActions.loadVorgangListSuccess( - { vorgangList }, - ); + const action: VorgangListAction & Action<string> = VorgangActions.loadVorgangListSuccess({ vorgangList }); it('should set loaded resource', () => { const state: VorgangState = reducer(initialState, action); @@ -207,10 +195,7 @@ describe('Vorgang Reducer', () => { }); it('should add vorgaenge', () => { - const state: VorgangState = reducer( - { ...initialState, vorgaenge: [createVorgangResource()] }, - action, - ); + const state: VorgangState = reducer({ ...initialState, vorgaenge: [createVorgangResource()] }, action); expect(state.vorgaenge.length).toBe(11); }); @@ -251,8 +236,7 @@ describe('Vorgang Reducer', () => { ...createVorgangListResourceWithResource(vorgaenge), statistic, }; - const action: VorgangListAction & Action<string> = - VorgangActions.searchVorgaengeBySuccess({ vorgangList }); + const action: VorgangListAction & Action<string> = VorgangActions.searchVorgaengeBySuccess({ vorgangList }); it('should set vorgangList', () => { const state: VorgangState = reducer(initialState, action); @@ -501,10 +485,7 @@ describe('Vorgang Reducer', () => { ...createCommandResource(), order: CommandOrder.REDIRECT_VORGANG, }; - const commandList: CommandListResource = createCommandListResource([ - createCommandResource(), - forwardCommand, - ]); + const commandList: CommandListResource = createCommandListResource([createCommandResource(), forwardCommand]); const action = VorgangActions.loadPendingCommandListSuccess({ commandList }); const state: VorgangState = reducer(initialState, action); @@ -525,9 +506,7 @@ describe('Vorgang Reducer', () => { const state: VorgangState = reducer(initialState, action); - expect(state.sendPostfachNachrichtPendingCommand.resource).toBe( - sendPostfachNachrichtCommand, - ); + expect(state.sendPostfachNachrichtPendingCommand.resource).toBe(sendPostfachNachrichtCommand); }); }); }); @@ -545,8 +524,7 @@ describe('Vorgang Reducer', () => { describe('on "setForwardingSingleCommand" action', () => { it('should set forward pending command', () => { - const commandStateResource: StateResource<CommandResource> = - createStateResource(createCommandResource()); + const commandStateResource: StateResource<CommandResource> = createStateResource(createCommandResource()); const action = VorgangActions.setForwardingSingleCommand({ commandStateResource }); const state: VorgangState = reducer(initialState, action); @@ -569,8 +547,7 @@ describe('Vorgang Reducer', () => { describe('on "setSendPostfachNachrichtSingleCommand" action', () => { it('should set forward pending command', () => { - const commandStateResource: StateResource<CommandResource> = - createStateResource(createCommandResource()); + const commandStateResource: StateResource<CommandResource> = createStateResource(createCommandResource()); const action = VorgangActions.setSendPostfachNachrichtSingleCommand({ commandStateResource, }); @@ -710,10 +687,7 @@ describe('Vorgang Reducer', () => { order: CommandOrder.VORGANG_ANNEHMEN, }; - const statusCommandMap: StatusCommandMap = Reducer.getStatusCommandMapByCreateCommand( - initialState, - command, - ); + const statusCommandMap: StatusCommandMap = Reducer.getStatusCommandMapByCreateCommand(initialState, command); expect(statusCommandMap[command.order].loading).toBeTruthy(); }); @@ -723,10 +697,7 @@ describe('Vorgang Reducer', () => { it('should return state value', () => { const command: CommandResource = { ...createCommandResource(), order: 'quatsch' }; - const statusCommandMap: StatusCommandMap = Reducer.getStatusCommandMapByCreateCommand( - initialState, - command, - ); + const statusCommandMap: StatusCommandMap = Reducer.getStatusCommandMapByCreateCommand(initialState, command); expect(statusCommandMap).toBe(initialState.statusCommandMap); }); @@ -741,8 +712,7 @@ describe('Vorgang Reducer', () => { order: CommandOrder.VORGANG_ANNEHMEN, }; - const statusCommandMap: StatusCommandMap = - Reducer.getStatusCommandMapByCreateCommandSuccess(initialState, command); + const statusCommandMap: StatusCommandMap = Reducer.getStatusCommandMapByCreateCommandSuccess(initialState, command); expect(statusCommandMap[command.order].resource).toBe(command); }); @@ -752,8 +722,7 @@ describe('Vorgang Reducer', () => { it('should return state value', () => { const command: CommandResource = { ...createCommandResource(), order: 'quatsch' }; - const statusCommandMap: StatusCommandMap = - Reducer.getStatusCommandMapByCreateCommandSuccess(initialState, command); + const statusCommandMap: StatusCommandMap = Reducer.getStatusCommandMapByCreateCommandSuccess(initialState, command); expect(statusCommandMap).toBe(initialState.statusCommandMap); }); @@ -768,8 +737,10 @@ describe('Vorgang Reducer', () => { order: CommandOrder.ASSIGN_USER, }; - const assignUserCommand: StateResource<CommandResource> = - Reducer.getAssignUserCommandByCreateCommand(initialState, command); + const assignUserCommand: StateResource<CommandResource> = Reducer.getAssignUserCommandByCreateCommand( + initialState, + command, + ); expect(assignUserCommand.loading).toBeTruthy(); }); @@ -779,8 +750,10 @@ describe('Vorgang Reducer', () => { it('should return state value', () => { const command: CommandResource = { ...createCommandResource(), order: 'quatsch' }; - const assignUserCommand: StateResource<CommandResource> = - Reducer.getAssignUserCommandByCreateCommand(initialState, command); + const assignUserCommand: StateResource<CommandResource> = Reducer.getAssignUserCommandByCreateCommand( + initialState, + command, + ); expect(assignUserCommand).toBe(initialState.assignUserCommand); }); @@ -795,8 +768,10 @@ describe('Vorgang Reducer', () => { order: CommandOrder.ASSIGN_USER, }; - const assignUserCommand: StateResource<CommandResource> = - Reducer.getAssignUserCommandByCreateCommandSuccess(initialState, command); + const assignUserCommand: StateResource<CommandResource> = Reducer.getAssignUserCommandByCreateCommandSuccess( + initialState, + command, + ); expect(assignUserCommand.resource).toBe(command); }); @@ -806,8 +781,10 @@ describe('Vorgang Reducer', () => { it('should return state value', () => { const command: CommandResource = { ...createCommandResource(), order: 'quatsch' }; - const assignUserCommand: StateResource<CommandResource> = - Reducer.getAssignUserCommandByCreateCommandSuccess(initialState, command); + const assignUserCommand: StateResource<CommandResource> = Reducer.getAssignUserCommandByCreateCommandSuccess( + initialState, + command, + ); expect(assignUserCommand).toBe(initialState.assignUserCommand); }); @@ -881,10 +858,7 @@ describe('Vorgang Reducer', () => { it('should clear stateResource', () => { const action = VorgangActions.exportVorgangSuccess(); - const state: VorgangState = reducer( - { ...initialState, vorgangExport: createEmptyStateResource(true) }, - action, - ); + const state: VorgangState = reducer({ ...initialState, vorgangExport: createEmptyStateResource(true) }, action); expect(state.vorgangExport).toEqual(createStateResource(true)); }); @@ -897,10 +871,7 @@ describe('Vorgang Reducer', () => { const action = NavigationActions.updateCurrentRouteData({ routeData: buildCurrentRouteData('search like me'), }); - const state: VorgangState = reducer( - { ...initialState, searchString: 'existingSearchString' }, - action, - ); + const state: VorgangState = reducer({ ...initialState, searchString: 'existingSearchString' }, action); expect(state.searchPreviewList.reload).toBeTruthy(); }); @@ -943,25 +914,18 @@ describe('Vorgang Reducer', () => { }); it('should be updated by given vorgangFilter and default view', () => { - const urlSegments: UrlSegment[] = [ - <any>{ path: ROUTE_PARAM_BY_VORGANG_FILTER[VorgangFilter.ALLE] }, - ]; + const urlSegments: UrlSegment[] = [<any>{ path: ROUTE_PARAM_BY_VORGANG_FILTER[VorgangFilter.ALLE] }]; const routeData: RouteData = { ...createRouteData(), urlSegments }; const localStorageSpy = jest.spyOn(Reducer, 'updateLocalStorage'); Reducer.updateByRouteData(initialState, routeData); - expect(localStorageSpy).toHaveBeenCalledWith( - VorgangFilter.ALLE, - VorgangView.VORGANG_LIST, - ); + expect(localStorageSpy).toHaveBeenCalledWith(VorgangFilter.ALLE, VorgangView.VORGANG_LIST); }); }); describe('filter only', () => { - const urlSegments: UrlSegment[] = [ - <any>{ path: ROUTE_PARAM_BY_VORGANG_FILTER[VorgangFilter.ALLE] }, - ]; + const urlSegments: UrlSegment[] = [<any>{ path: ROUTE_PARAM_BY_VORGANG_FILTER[VorgangFilter.ALLE] }]; const routeData: RouteData = { ...createRouteData(), urlSegments }; it('should set reload vorgangList', () => { @@ -979,9 +943,7 @@ describe('Vorgang Reducer', () => { it('should set vorgangFilter', () => { const routeData: RouteData = { ...createRouteData(), - urlSegments: [ - <any>{ path: ROUTE_PARAM_BY_VORGANG_FILTER[VorgangFilter.MEINE_VORGAENGE] }, - ], + urlSegments: [<any>{ path: ROUTE_PARAM_BY_VORGANG_FILTER[VorgangFilter.MEINE_VORGAENGE] }], }; const state: VorgangState = Reducer.updateByRouteData(initialState, routeData); @@ -992,7 +954,7 @@ describe('Vorgang Reducer', () => { it('should clear vorgaenge', () => { const state: VorgangState = Reducer.updateByRouteData(initialState, routeData); - expect(state.vorgaenge).toBe(EMPTY_ARRAY); + expect(state.vorgaenge).toEqual([]); }); }); @@ -1024,7 +986,7 @@ describe('Vorgang Reducer', () => { it('should clear vorgaenge', () => { const state: VorgangState = Reducer.updateByRouteData(initialState, routeData); - expect(state.vorgaenge).toBe(EMPTY_ARRAY); + expect(state.vorgaenge).toEqual([]); }); }); }); @@ -1057,65 +1019,56 @@ describe('Vorgang Reducer', () => { describe('vorgangStatistic', () => { describe('byStatus', () => { it('should have null as neu', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.byStatus.neu).toBeNull(); }); it('should have null as angenommen', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.byStatus.angenommen).toBeNull(); }); it('should have null as inBearbeitung', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.byStatus.inBearbeitung).toBeNull(); }); it('should have null as beschieden', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.byStatus.beschieden).toBeNull(); }); it('should have null as abgeschlossen', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.byStatus.abgeschlossen).toBeNull(); }); it('should have null as verworfen', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.byStatus.verworfen).toBeNull(); }); it('should have null as zuLoeschen', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.byStatus.zuLoeschen).toBeNull(); }); }); it('should have null as wiedervorlagen', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.wiedervorlagen).toBeNull(); }); it('should have false as existsWiedervorlageOverdue', () => { - const vorgangStatistic: StateResource<VorgangStatistic> = - Reducer.initialState.vorgangStatistic; + const vorgangStatistic: StateResource<VorgangStatistic> = Reducer.initialState.vorgangStatistic; expect(vorgangStatistic.resource.existsWiedervorlageOverdue).toBeFalsy(); }); diff --git a/alfa-client/libs/vorgang-shared/src/lib/+state/vorgang.reducer.ts b/alfa-client/libs/vorgang-shared/src/lib/+state/vorgang.reducer.ts index 33bc13ea4c0b70755cc6df22869c44eb794af8eb..0f200a551da43386ab80c67938d561a277bed11d 100644 --- a/alfa-client/libs/vorgang-shared/src/lib/+state/vorgang.reducer.ts +++ b/alfa-client/libs/vorgang-shared/src/lib/+state/vorgang.reducer.ts @@ -21,15 +21,8 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - removeLocalStorageView, - setFilterIntoStorage, - setViewIntoStorage, -} from '@alfa-client/app-shared'; -import { - BinaryFileListResource, - LoadBinaryFileListSuccessProps, -} from '@alfa-client/binary-file-shared'; +import { removeLocalStorageView, setFilterIntoStorage, setViewIntoStorage } from '@alfa-client/app-shared'; +import { BinaryFileListResource, LoadBinaryFileListSuccessProps } from '@alfa-client/binary-file-shared'; import { CommandOrder, CommandProps, @@ -43,7 +36,6 @@ import { import { RouteData } from '@alfa-client/navigation-shared'; import { ApiErrorAction, - EMPTY_ARRAY, EMPTY_STRING, StateResource, createEmptyStateResource, @@ -72,12 +64,7 @@ import { VorgangWithEingangResource, } from '../vorgang.model'; import { getVorgaengeFromList, isAssignUserCommand, isStatusCommand } from '../vorgang.util'; -import { - HttpErrorAction, - StringBasedProps, - VorgangListAction, - VorgangWithEingangAction, -} from './vorgang.actions'; +import { HttpErrorAction, StringBasedProps, VorgangListAction, VorgangWithEingangAction } from './vorgang.actions'; import * as CommandActions from '../../../../command-shared/src/lib/+state/command.actions'; import * as NavigationActions from '../../../../navigation-shared/src/lib/+state/navigation.actions'; @@ -113,7 +100,7 @@ export interface VorgangState { export const initialState: VorgangState = { vorgangList: createEmptyStateResource(), vorgangStatistic: createStateResource(createEmptyVorgangStatistic()), - vorgaenge: EMPTY_ARRAY, + vorgaenge: [], searchString: EMPTY_STRING, searchPreviewList: createEmptyStateResource(), vorgangView: VorgangView.VORGANG_LIST, @@ -197,7 +184,7 @@ const vorgangReducer: ActionReducer<VorgangState, Action> = createReducer( (state: VorgangState): VorgangState => ({ ...state, vorgangList: { ...state.vorgangList, loading: true }, - vorgaenge: EMPTY_ARRAY, + vorgaenge: [], searchPreviewList: createEmptyStateResource(), }), ), @@ -215,9 +202,7 @@ const vorgangReducer: ActionReducer<VorgangState, Action> = createReducer( VorgangActions.searchVorgaengeByFailure, (state: VorgangState, action: HttpErrorAction): VorgangState => ({ ...state, - vorgangList: createErrorStateResource( - getApiErrorFromHttpErrorResponse(action.httpErrorResponse), - ), + vorgangList: createErrorStateResource(getApiErrorFromHttpErrorResponse(action.httpErrorResponse)), searchPreviewList: createEmptyStateResource(), }), ), @@ -227,10 +212,7 @@ const vorgangReducer: ActionReducer<VorgangState, Action> = createReducer( (state: VorgangState, props: StringBasedProps): VorgangState => ({ ...state, searchString: props.string, - searchPreviewList: - clearPreviewList(props) ? - createEmptyStateResource() - : { ...state.searchPreviewList, reload: true }, + searchPreviewList: clearPreviewList(props) ? createEmptyStateResource() : { ...state.searchPreviewList, reload: true }, }), ), on( @@ -251,9 +233,7 @@ const vorgangReducer: ActionReducer<VorgangState, Action> = createReducer( VorgangActions.searchForPreviewFailure, (state: VorgangState, action: HttpErrorAction): VorgangState => ({ ...state, - searchPreviewList: createErrorStateResource( - getApiErrorFromHttpErrorResponse(action.httpErrorResponse), - ), + searchPreviewList: createErrorStateResource(getApiErrorFromHttpErrorResponse(action.httpErrorResponse)), }), ), @@ -347,9 +327,7 @@ const vorgangReducer: ActionReducer<VorgangState, Action> = createReducer( VorgangActions.loadPendingCommandListSuccess, (state: VorgangState, props: LoadCommandListSuccessProps): VorgangState => ({ ...state, - forwardPendingCommand: createStateResource( - getPendingCommandByOrder(props.commandList, [CommandOrder.REDIRECT_VORGANG]), - ), + forwardPendingCommand: createStateResource(getPendingCommandByOrder(props.commandList, [CommandOrder.REDIRECT_VORGANG])), sendPostfachNachrichtPendingCommand: createStateResource( getPendingCommandByOrder(props.commandList, [CommandOrder.SEND_POSTFACH_NACHRICHT]), ), @@ -420,21 +398,12 @@ const vorgangReducer: ActionReducer<VorgangState, Action> = createReducer( on(CommandActions.createCommandSuccess, (state, props: CommandProps): VorgangState => { return { ...state, - statusCommandMap: VorgangReducer.getStatusCommandMapByCreateCommandSuccess( - state, - props.command, - ), - assignUserCommand: VorgangReducer.getAssignUserCommandByCreateCommandSuccess( - state, - props.command, - ), + statusCommandMap: VorgangReducer.getStatusCommandMapByCreateCommandSuccess(state, props.command), + assignUserCommand: VorgangReducer.getAssignUserCommandByCreateCommandSuccess(state, props.command), /** * @deprecated Das Nachladen des Vorgangs im Service, nach erfolgreicher Beendigung des Commands, durchfuehren */ - vorgangWithEingang: VorgangReducer.getVorgangWithEingangStateResourceByCreateCommandSucces( - state, - props.command, - ), + vorgangWithEingang: VorgangReducer.getVorgangWithEingangStateResourceByCreateCommandSucces(state, props.command), }; }), on(CommandActions.revokeCommand, (state): VorgangState => { @@ -483,40 +452,27 @@ export function getVorgangWithEingangStateResourceByCreateCommandSucces( : state.vorgangWithEingang; } -export function getStatusCommandMapByCreateCommand( - state: VorgangState, - command: CreateCommand, -): StatusCommandMap { +export function getStatusCommandMapByCreateCommand(state: VorgangState, command: CreateCommand): StatusCommandMap { return isStatusCommand(command.order) ? { ...state.statusCommandMap, [command.order]: createEmptyStateResource(true) } : state.statusCommandMap; } -export function getStatusCommandMapByCreateCommandSuccess( - state: VorgangState, - command: CommandResource, -): StatusCommandMap { +export function getStatusCommandMapByCreateCommandSuccess(state: VorgangState, command: CommandResource): StatusCommandMap { return isStatusCommand(command.order) ? { ...state.statusCommandMap, [command.order]: createStateResource(command) } : state.statusCommandMap; } -export function getAssignUserCommandByCreateCommand( - state: VorgangState, - command: CreateCommand, -): StateResource<CommandResource> { - return isAssignUserCommand(command.order) ? - createEmptyStateResource(true) - : state.assignUserCommand; +export function getAssignUserCommandByCreateCommand(state: VorgangState, command: CreateCommand): StateResource<CommandResource> { + return isAssignUserCommand(command.order) ? createEmptyStateResource(true) : state.assignUserCommand; } export function getAssignUserCommandByCreateCommandSuccess( state: VorgangState, command: CommandResource, ): StateResource<CommandResource> { - return isAssignUserCommand(command.order) ? - createStateResource(command) - : state.assignUserCommand; + return isAssignUserCommand(command.order) ? createStateResource(command) : state.assignUserCommand; } function clearPreviewList(props: StringBasedProps): boolean { @@ -527,7 +483,7 @@ export function updateByRouteData(state: VorgangState, routeData: RouteData): Vo let newState = { ...state, vorgangList: { ...state.vorgangList, reload: true }, - vorgaenge: EMPTY_ARRAY, + vorgaenge: [], }; if (isUebersichtsSeite(routeData)) { @@ -539,10 +495,7 @@ export function updateByRouteData(state: VorgangState, routeData: RouteData): Vo return newState; } -function prepareStateOnVorgangListNavigation( - state: VorgangState, - routeData: RouteData, -): VorgangState { +function prepareStateOnVorgangListNavigation(state: VorgangState, routeData: RouteData): VorgangState { let newState: VorgangState = { ...state, vorgangFilter: getVorgangFilter(routeData), diff --git a/alfa-client/libs/vorgang-shared/src/lib/vorgang.util.ts b/alfa-client/libs/vorgang-shared/src/lib/vorgang.util.ts index 64a8f39b55caa29b47c651e43fde340d486902ab..806b5ea58f19c6a646ef6dcbd9a7c41c93b8dbd0 100644 --- a/alfa-client/libs/vorgang-shared/src/lib/vorgang.util.ts +++ b/alfa-client/libs/vorgang-shared/src/lib/vorgang.util.ts @@ -22,7 +22,7 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { CommandOrder, CreateCommand } from '@alfa-client/command-shared'; -import { EMPTY_ARRAY, isNotNil } from '@alfa-client/tech-shared'; +import { isNotNil } from '@alfa-client/tech-shared'; import { ResourceUri, getEmbeddedResource } from '@ngxp/rest'; import { isNull } from 'lodash-es'; import { VorgangListLinkRel } from './vorgang.linkrel'; @@ -72,13 +72,10 @@ export function createForwardCommand(redirectRequest: ForwardRequest): CreateFor export function getVorgaengeFromList(vorgangList: VorgangListResource): VorgangResource[] { if (isNotNil(vorgangList)) { - const embeddedResource: VorgangResource[] = getEmbeddedResource( - vorgangList, - VorgangListLinkRel.VORGANG_HEADER_LIST, - ); - return isNull(embeddedResource) ? EMPTY_ARRAY : embeddedResource; + const embeddedResource: VorgangResource[] = getEmbeddedResource(vorgangList, VorgangListLinkRel.VORGANG_HEADER_LIST); + return isNull(embeddedResource) ? [] : embeddedResource; } - return EMPTY_ARRAY; + return []; } export function createAssignUserCommand(assignedTo: ResourceUri): CreateAssignUserCommand { diff --git a/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.model.ts b/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.model.ts index 2da311b309c80898da51eca6ee5aa04714b8bfff..a9fbef74f589c2a26460d6e0b600a09d01f9f8d7 100644 --- a/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.model.ts +++ b/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.model.ts @@ -36,8 +36,11 @@ export interface Wiedervorlage { } export interface WiedervorlageResource extends Wiedervorlage, Resource {} + export interface WiedervorlageListResource extends ListResource {} export interface BinaryFileListByWiedervorlageUri { [uri: ResourceUri]: BehaviorSubject<StateResource<BinaryFileListResource>>; } + +export const WIEDERVORLAGE_UPLOADED_ATTACHMENTS = 'wiedervorlage_uploaded_attachments'; diff --git a/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.service.spec.ts b/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.service.spec.ts index b9cc7466f7eeb8425289ce68dc5e5fa0745a74bb..4d6c0e5ba0bca3a14b4e33d989d737fa379552ff 100644 --- a/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.service.spec.ts +++ b/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.service.spec.ts @@ -21,49 +21,28 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { BinaryFileListResource, BinaryFileService } from '@alfa-client/binary-file-shared'; +import { BinaryFileService } from '@alfa-client/binary-file-shared'; import { CommandOrder, CommandResource, CommandService } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { - StateResource, - createEmptyStateResource, - createStateResource, - decodeUrlFromEmbedding, -} from '@alfa-client/tech-shared'; +import * as TechShared from '@alfa-client/tech-shared'; +import { createEmptyStateResource, createStateResource, decodeUrlFromEmbedding, StateResource } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { SnackBarService } from '@alfa-client/ui'; -import { - VorgangResource, - VorgangService, - VorgangWithEingangResource, -} from '@alfa-client/vorgang-shared'; +import { VorgangResource, VorgangService, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; +import { expect } from '@jest/globals'; import { getUrl } from '@ngxp/rest'; import { cold, hot } from 'jest-marbles'; -import { createBinaryFileListResource } from 'libs/binary-file-shared/test/binary-file'; import { CommandLinkRel } from 'libs/command-shared/src/lib/command.linkrel'; import { createCommandResource } from 'libs/command-shared/test/command'; -import { - createVorgangResource, - createVorgangWithEingangResource, -} from 'libs/vorgang-shared/test/vorgang'; -import { - createWiedervorlage, - createWiedervorlageListResource, - createWiedervorlageResource, -} from 'libs/wiedervorlage-shared/test/wiedervorlage'; +import { createVorgangResource, createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; +import { createWiedervorlage, createWiedervorlageListResource, createWiedervorlageResource, } from 'libs/wiedervorlage-shared/test/wiedervorlage'; import { of } from 'rxjs'; import { WiedervorlageLinkRel, WiedervorlageListLinkRel } from './wiedervorlage.linkrel'; import { WiedervorlageMessages } from './wiedervorlage.message'; -import { - Wiedervorlage, - WiedervorlageListResource, - WiedervorlageResource, -} from './wiedervorlage.model'; +import { Wiedervorlage, WIEDERVORLAGE_UPLOADED_ATTACHMENTS, WiedervorlageListResource, WiedervorlageResource, } from './wiedervorlage.model'; import { WiedervorlageRepository } from './wiedervorlage.repository'; import { WiedervorlageService } from './wiedervorlage.service'; -import * as TechShared from '@alfa-client/tech-shared'; - jest.mock('@alfa-client/tech-shared', () => mockAsEsModule('@alfa-client/tech-shared')); function mockAsEsModule(module: string) { @@ -86,9 +65,7 @@ describe('WiedervorlageService', () => { const wiedervorlageResource: WiedervorlageResource = createWiedervorlageResource(); const wiedervorlage: Wiedervorlage = createWiedervorlage(); const commandResource: CommandResource = createCommandResource(); - const commandResourceWithEffectedResourceLink: CommandResource = createCommandResource([ - CommandLinkRel.EFFECTED_RESOURCE, - ]); + const commandResourceWithEffectedResourceLink: CommandResource = createCommandResource([CommandLinkRel.EFFECTED_RESOURCE]); beforeEach(() => { repository = mock(WiedervorlageRepository); @@ -112,11 +89,9 @@ describe('WiedervorlageService', () => { describe('getWiedervorlageList', () => { const wiedervorlageList: WiedervorlageListResource = createWiedervorlageListResource(); - const wiedervorlageListStateResource: StateResource<WiedervorlageListResource> = - createStateResource(wiedervorlageList); + const wiedervorlageListStateResource: StateResource<WiedervorlageListResource> = createStateResource(wiedervorlageList); - const vorgangWithEingangStateResource: StateResource<VorgangWithEingangResource> = - createStateResource(vorgangResource); + const vorgangWithEingangStateResource: StateResource<VorgangWithEingangResource> = createStateResource(vorgangResource); beforeEach(() => { vorgangService.getVorgangWithEingang.mockReturnValue(of(vorgangWithEingangStateResource)); @@ -129,18 +104,12 @@ describe('WiedervorlageService', () => { }); it('should return values', () => { - vorgangService.getVorgangWithEingang.mockReturnValue( - hot('-a', { a: vorgangWithEingangStateResource }), - ); - service.wiedervorlageList$.asObservable = jest - .fn() - .mockReturnValue(hot('-a', { a: wiedervorlageListStateResource })); + vorgangService.getVorgangWithEingang.mockReturnValue(hot('-a', { a: vorgangWithEingangStateResource })); + service.wiedervorlageList$.asObservable = jest.fn().mockReturnValue(hot('-a', { a: wiedervorlageListStateResource })); const result = service.getWiedervorlageList(); - expect(result).toBeObservable( - cold('ab', { a: createEmptyStateResource(true), b: wiedervorlageListStateResource }), - ); + expect(result).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: wiedervorlageListStateResource })); }); it.skip('should call loadWiedervorlagenByVorgang if required', (done) => { @@ -159,9 +128,7 @@ describe('WiedervorlageService', () => { it('should NOT call loadWiedervorlagenByVorgang if already loaded', () => { service.loadWiedervorlagenByVorgang = jest.fn(); - service.wiedervorlageList$.asObservable = jest - .fn() - .mockReturnValue(of(wiedervorlageListStateResource)); + service.wiedervorlageList$.asObservable = jest.fn().mockReturnValue(of(wiedervorlageListStateResource)); service.getWiedervorlageList(); @@ -176,9 +143,7 @@ describe('WiedervorlageService', () => { createStateResource(wiedervorlageListResource); it('should return initial value', () => { - service.wiedervorlageList$.asObservable = jest - .fn() - .mockReturnValue(hot('-a', { a: wiedervorlageListResource })); + service.wiedervorlageList$.asObservable = jest.fn().mockReturnValue(hot('-a', { a: wiedervorlageListResource })); const result = service.getWiedervorlageListByGivenVorgang(vorgang); @@ -206,8 +171,7 @@ describe('WiedervorlageService', () => { describe('getWiedervorlage', () => { const wiedervorlage: WiedervorlageResource = createWiedervorlageResource(); - const wiedervorlageStateResource: StateResource<WiedervorlageResource> = - createStateResource(wiedervorlage); + const wiedervorlageStateResource: StateResource<WiedervorlageResource> = createStateResource(wiedervorlage); beforeEach(() => { navigationService.getDecodedParam.mockReturnValue(getUrl(wiedervorlageResource)); @@ -215,15 +179,11 @@ describe('WiedervorlageService', () => { }); it('should return initial values', () => { - service.wiedervorlage$.asObservable = jest - .fn() - .mockReturnValue(hot('-a', { a: wiedervorlageStateResource })); + service.wiedervorlage$.asObservable = jest.fn().mockReturnValue(hot('-a', { a: wiedervorlageStateResource })); const result = service.getWiedervorlage(); - expect(result).toBeObservable( - cold('ab', { a: createEmptyStateResource(true), b: wiedervorlageStateResource }), - ); + expect(result).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: wiedervorlageStateResource })); }); it('should set loading to true', () => { @@ -237,9 +197,7 @@ describe('WiedervorlageService', () => { it('should call navigationService', () => { service.getWiedervorlage().subscribe(); - expect(navigationService.getDecodedParam).toHaveBeenCalledWith( - WiedervorlageService.encodedWiedervorlageUriParam, - ); + expect(navigationService.getDecodedParam).toHaveBeenCalledWith(WiedervorlageService.encodedWiedervorlageUriParam); }); it('should call repository', () => { @@ -257,9 +215,7 @@ describe('WiedervorlageService', () => { describe('saveWiedervorlage', () => { beforeEach(() => { - commandService.createCommand.mockReturnValue( - of(createStateResource(commandResourceWithEffectedResourceLink)), - ); + commandService.createCommand.mockReturnValue(of(createStateResource(commandResourceWithEffectedResourceLink))); }); it('should return initial value and mapped value', () => { @@ -280,11 +236,10 @@ describe('WiedervorlageService', () => { it('should call command service', () => { service.saveWiedervorlage(wiedervorlageResource, wiedervorlage).subscribe(); - expect(commandService.createCommand).toHaveBeenCalledWith( - wiedervorlageResource, - WiedervorlageLinkRel.EDIT, - { order: CommandOrder.EDIT_WIEDERVORLAGE, body: wiedervorlage }, - ); + expect(commandService.createCommand).toHaveBeenCalledWith(wiedervorlageResource, WiedervorlageLinkRel.EDIT, { + order: CommandOrder.EDIT_WIEDERVORLAGE, + body: wiedervorlage, + }); }); it('should set submit-in-progress on loading', () => { @@ -300,10 +255,7 @@ describe('WiedervorlageService', () => { service.saveWiedervorlage(wiedervorlageResource, wiedervorlage).subscribe(); - const expectedMessage = WiedervorlageMessages.SAVED.replace( - '{betreff}', - wiedervorlage.betreff, - ); + const expectedMessage = WiedervorlageMessages.SAVED.replace('{betreff}', wiedervorlage.betreff); expect(service.proceedAfterReceiveCommand).toHaveBeenCalledWith( createStateResource(commandResourceWithEffectedResourceLink), expectedMessage, @@ -317,15 +269,11 @@ describe('WiedervorlageService', () => { }); it('should return intitial value and mapped value', () => { - commandService.createCommand.mockReturnValue( - hot('-a', { a: createStateResource(commandResource) }), - ); + commandService.createCommand.mockReturnValue(hot('-a', { a: createStateResource(commandResource) })); const result = service.createWiedervorlage(wiedervorlage); - expect(result).toBeObservable( - cold('ab', { a: createEmptyStateResource(true), b: createStateResource(commandResource) }), - ); + expect(result).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: createStateResource(commandResource) })); }); it('should set submit-in-progress on loading', () => { @@ -352,23 +300,18 @@ describe('WiedervorlageService', () => { service.createWiedervorlage(wiedervorlage).subscribe(); - const expectedMessage = WiedervorlageMessages.CREATED.replace( - '{betreff}', - wiedervorlage.betreff, - ); - expect(service.proceedAfterReceiveCommand).toHaveBeenCalledWith( - createStateResource(commandResource), - expectedMessage, - ); + const expectedMessage = WiedervorlageMessages.CREATED.replace('{betreff}', wiedervorlage.betreff); + expect(service.proceedAfterReceiveCommand).toHaveBeenCalledWith(createStateResource(commandResource), expectedMessage); }); }); describe('proceedAfterRecieveCommand', () => { + beforeEach(() => { + service.clearUploadedFiles = jest.fn(); + }); + it('should set reload on wiedervorlageList', () => { - service.proceedAfterReceiveCommand( - createStateResource(commandResourceWithEffectedResourceLink), - null, - ); + service.proceedAfterReceiveCommand(createStateResource(commandResourceWithEffectedResourceLink), null); expect(service.wiedervorlageList$.value.reload).toBe(true); }); @@ -383,15 +326,15 @@ describe('WiedervorlageService', () => { snackbarService.show = jest.fn(); const snackbarMessage: string = 'XX wurde XX'; - service.proceedAfterReceiveCommand( - createStateResource(commandResourceWithEffectedResourceLink), - snackbarMessage, - ); + service.proceedAfterReceiveCommand(createStateResource(commandResourceWithEffectedResourceLink), snackbarMessage); - expect(snackbarService.show).toHaveBeenCalledWith( - commandResourceWithEffectedResourceLink, - snackbarMessage, - ); + expect(snackbarService.show).toHaveBeenCalledWith(commandResourceWithEffectedResourceLink, snackbarMessage); + }); + + it('should clear uploaded files', () => { + service.proceedAfterReceiveCommand(createStateResource(commandResourceWithEffectedResourceLink), null); + + expect(service.clearUploadedFiles).toHaveBeenCalled(); }); }); @@ -399,24 +342,22 @@ describe('WiedervorlageService', () => { const wiedervorlageResource: WiedervorlageResource = createWiedervorlageResource(); beforeEach(() => { - commandService.createCommand.mockReturnValue( - of(createStateResource(commandResourceWithEffectedResourceLink)), - ); + commandService.createCommand.mockReturnValue(of(createStateResource(commandResourceWithEffectedResourceLink))); (<any>service).reloadWiedervorlageList = jest.fn(); (<any>service).loadAndSetWiedervorlageByUrl = jest.fn(); service.wiedervorlage$.next(createStateResource(wiedervorlageResource)); + service.clearUploadedFiles = jest.fn(); }); describe('wiedervorlage erledigen', () => { it('should call commandService', () => { service.erledigen(); - expect(commandService.createCommand).toHaveBeenCalledWith( - wiedervorlageResource, - WiedervorlageLinkRel.ERLEDIGEN, - { order: CommandOrder.WIEDERVORLAGE_ERLEDIGEN, body: null }, - ); + expect(commandService.createCommand).toHaveBeenCalledWith(wiedervorlageResource, WiedervorlageLinkRel.ERLEDIGEN, { + order: CommandOrder.WIEDERVORLAGE_ERLEDIGEN, + body: null, + }); }); it('should reload wiedervorlage', () => { @@ -433,17 +374,22 @@ describe('WiedervorlageService', () => { `Die Wiedervorlage ${wiedervorlageResource.betreff} wurde erledigt`, ); }); + + it('should clear uploaded files', () => { + service.erledigen(); + + expect(service.clearUploadedFiles).toHaveBeenCalled(); + }); }); describe('wiedervorlage wiedereroeffnen', () => { it('should call commandService', () => { service.wiedereroeffnen(); - expect(commandService.createCommand).toHaveBeenCalledWith( - wiedervorlageResource, - WiedervorlageLinkRel.WIEDEREROEFFNEN, - { order: CommandOrder.WIEDERVORLAGE_WIEDEREROEFFNEN, body: null }, - ); + expect(commandService.createCommand).toHaveBeenCalledWith(wiedervorlageResource, WiedervorlageLinkRel.WIEDEREROEFFNEN, { + order: CommandOrder.WIEDERVORLAGE_WIEDEREROEFFNEN, + body: null, + }); }); it('should reload wiedervorlage', () => { @@ -460,6 +406,12 @@ describe('WiedervorlageService', () => { `Die Wiedervorlage ${wiedervorlageResource.betreff} wurde wiedereröffnet`, ); }); + + it('should clear uploaded files', () => { + service.wiedereroeffnen(); + + expect(service.clearUploadedFiles).toHaveBeenCalled(); + }); }); }); @@ -467,9 +419,7 @@ describe('WiedervorlageService', () => { const message: string = 'SnackbarMessage'; it('should call service if command is done', () => { - const commandResource: CommandResource = createCommandResource([ - CommandLinkRel.EFFECTED_RESOURCE, - ]); + const commandResource: CommandResource = createCommandResource([CommandLinkRel.EFFECTED_RESOURCE]); service.showSnackBar(commandResource, message); @@ -491,8 +441,14 @@ describe('WiedervorlageService', () => { service.setWiedervorlageListReload = jest.fn(); (<any>service).forceWiedervorlageReload = jest.fn(); - service.clearAttachmentList = jest.fn(); service.clearWiedervorlagenList = jest.fn(); + service.clearUploadedFiles = jest.fn(); + }); + + it('should clear uploaded files', () => { + service.onNavigation({}); + + expect(service.clearUploadedFiles).toHaveBeenCalled(); }); describe('to vorgang detail', () => { @@ -511,12 +467,6 @@ describe('WiedervorlageService', () => { expect((<any>service).forceWiedervorlageReload).toHaveBeenCalled(); }); - - it('should clear attachments', () => { - service.onNavigation({}); - - expect(service.clearAttachmentList).toHaveBeenCalled(); - }); }); describe('to vorgang list', () => { @@ -525,48 +475,6 @@ describe('WiedervorlageService', () => { expect(service.clearWiedervorlagenList).toHaveBeenCalled(); }); - - it('should clear attachments', () => { - service.onNavigation({}); - - expect(service.clearAttachmentList).toHaveBeenCalled(); - }); - }); - }); - - describe('load attachments', () => { - const binaryFileListResource: BinaryFileListResource = createBinaryFileListResource(); - const binaryFileStateResource: StateResource<BinaryFileListResource> = - createStateResource(binaryFileListResource); - - beforeEach(() => { - binaryFileService.getFiles.mockReturnValue(of(binaryFileStateResource)); - service.setAttachmentLoading = jest.fn(); - }); - - it('should set attachment state resource on loading', () => { - service.loadAttachments(wiedervorlageResource); - - expect(service.setAttachmentLoading).toHaveBeenCalledWith(wiedervorlageResource); - }); - - it('should call file service', () => { - service.loadAttachments(wiedervorlageResource); - - expect(binaryFileService.getFiles).toHaveBeenCalledWith( - wiedervorlageResource, - WiedervorlageLinkRel.ATTACHMENTS, - ); - }); - - it('should set loaded resource into state resource', () => { - service.loadAttachments(wiedervorlageResource); - - service.getAttachmentList(wiedervorlageResource); - const result: StateResource<BinaryFileListResource> = - service.attachmentListByWiedervorlage[getUrl(wiedervorlageResource)].value; - - expect(result).toEqual(binaryFileStateResource); }); }); @@ -618,4 +526,12 @@ describe('WiedervorlageService', () => { expect(repository.getWiedervorlage).toHaveBeenCalledWith(decodedUrl); }); }); + + describe('clearUploadedFiles', () => { + it('should call binary file service', () => { + service.clearUploadedFiles(); + + expect(binaryFileService.clearUploadedFiles).toHaveBeenCalledWith(WIEDERVORLAGE_UPLOADED_ATTACHMENTS); + }); + }); }); diff --git a/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.service.ts b/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.service.ts index 691da275eaed51ea51269561b8c979daa388153a..208bf667e34a682bf13a4022276667409c9f4831 100644 --- a/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.service.ts +++ b/alfa-client/libs/wiedervorlage-shared/src/lib/wiedervorlage.service.ts @@ -21,42 +21,21 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { BinaryFileListResource, BinaryFileService } from '@alfa-client/binary-file-shared'; -import { - CommandOrder, - CommandResource, - CommandService, - CreateCommand, - isDone, -} from '@alfa-client/command-shared'; +import { BinaryFileService } from '@alfa-client/binary-file-shared'; +import { CommandOrder, CommandResource, CommandService, CreateCommand, isDone } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { - StateResource, - createEmptyStateResource, - createStateResource, - decodeUrlFromEmbedding, - doIfLoadingRequired, - hasStateResourceError, - isNotNull, - isNotUndefined, - replacePlaceholder, -} from '@alfa-client/tech-shared'; +import { createEmptyStateResource, createStateResource, decodeUrlFromEmbedding, doIfLoadingRequired, hasStateResourceError, isNotNull, isNotUndefined, replacePlaceholder, StateResource, } from '@alfa-client/tech-shared'; import { SnackBarService } from '@alfa-client/ui'; import { VorgangHeaderLinkRel, VorgangResource, VorgangService } from '@alfa-client/vorgang-shared'; import { Injectable, OnDestroy } from '@angular/core'; import { Params } from '@angular/router'; -import { ResourceUri, getUrl, hasLink } from '@ngxp/rest'; +import { hasLink, ResourceUri } from '@ngxp/rest'; import { isEqual, isNil, isNull, isUndefined } from 'lodash-es'; -import { BehaviorSubject, Observable, Subscription, combineLatest } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; import { filter, map, startWith, tap } from 'rxjs/operators'; import { WiedervorlageLinkRel, WiedervorlageListLinkRel } from './wiedervorlage.linkrel'; import { WiedervorlageMessages } from './wiedervorlage.message'; -import { - BinaryFileListByWiedervorlageUri, - Wiedervorlage, - WiedervorlageListResource, - WiedervorlageResource, -} from './wiedervorlage.model'; +import { Wiedervorlage, WIEDERVORLAGE_UPLOADED_ATTACHMENTS, WiedervorlageListResource, WiedervorlageResource, } from './wiedervorlage.model'; import { WiedervorlageRepository } from './wiedervorlage.repository'; import { WiedervorlageRoutes } from './wiedervorlage.route'; import { createErledigenCommand, createWiedereroeffnenCommand } from './wiedervorlage.util'; @@ -65,14 +44,12 @@ import { createErledigenCommand, createWiedereroeffnenCommand } from './wiedervo export class WiedervorlageService implements OnDestroy { static encodedWiedervorlageUriParam: string = 'wiedervorlageUrl'; - readonly wiedervorlageList$: BehaviorSubject<StateResource<WiedervorlageListResource>> = - new BehaviorSubject<StateResource<WiedervorlageListResource>>( - createEmptyStateResource<WiedervorlageListResource>(), - ); - readonly wiedervorlage$: BehaviorSubject<StateResource<WiedervorlageResource>> = - new BehaviorSubject<StateResource<WiedervorlageResource>>( - createEmptyStateResource<WiedervorlageResource>(), - ); + readonly wiedervorlageList$: BehaviorSubject<StateResource<WiedervorlageListResource>> = new BehaviorSubject< + StateResource<WiedervorlageListResource> + >(createEmptyStateResource<WiedervorlageListResource>()); + readonly wiedervorlage$: BehaviorSubject<StateResource<WiedervorlageResource>> = new BehaviorSubject< + StateResource<WiedervorlageResource> + >(createEmptyStateResource<WiedervorlageResource>()); readonly submitInProgress$: BehaviorSubject<StateResource<CommandResource>> = new BehaviorSubject< StateResource<CommandResource> @@ -81,15 +58,11 @@ export class WiedervorlageService implements OnDestroy { readonly erledigenCommand$: BehaviorSubject<StateResource<CommandResource>> = new BehaviorSubject< StateResource<CommandResource> >(createEmptyStateResource<CommandResource>()); - readonly wiedereroeffnenCommand$: BehaviorSubject<StateResource<CommandResource>> = - new BehaviorSubject<StateResource<CommandResource>>( - createEmptyStateResource<CommandResource>(), - ); - - attachmentListByWiedervorlage = <BinaryFileListByWiedervorlageUri>{}; + readonly wiedereroeffnenCommand$: BehaviorSubject<StateResource<CommandResource>> = new BehaviorSubject< + StateResource<CommandResource> + >(createEmptyStateResource<CommandResource>()); private subscription: Subscription; - private attachmentSubscription: Subscription; constructor( private repository: WiedervorlageRepository, @@ -103,15 +76,10 @@ export class WiedervorlageService implements OnDestroy { } public getWiedervorlageList(): Observable<StateResource<WiedervorlageListResource>> { - return combineLatest([ - this.wiedervorlageList$.asObservable(), - this.vorgangService.getVorgangWithEingang(), - ]).pipe( + return combineLatest([this.wiedervorlageList$.asObservable(), this.vorgangService.getVorgangWithEingang()]).pipe( tap(([wiedervorlageList, vorgangResource]) => { if (isNotNull(vorgangResource.resource)) { - doIfLoadingRequired(wiedervorlageList, () => - this.loadWiedervorlagenByVorgang(vorgangResource.resource), - ); + doIfLoadingRequired(wiedervorlageList, () => this.loadWiedervorlagenByVorgang(vorgangResource.resource)); } }), map(([wiedervorlageList]) => wiedervorlageList), @@ -122,22 +90,18 @@ export class WiedervorlageService implements OnDestroy { public getWiedervorlageListByGivenVorgang( vorgangResource: VorgangResource, ): Observable<StateResource<WiedervorlageListResource>> { - doIfLoadingRequired(this.wiedervorlageList$.value, () => - this.loadWiedervorlagenByVorgang(vorgangResource), - ); + doIfLoadingRequired(this.wiedervorlageList$.value, () => this.loadWiedervorlagenByVorgang(vorgangResource)); return this.wiedervorlageList$.asObservable(); } loadWiedervorlagenByVorgang(vorgangResource: VorgangResource): void { if (hasLink(vorgangResource, VorgangHeaderLinkRel.WIEDERVORLAGEN)) { - const subscription: Subscription = this.repository - .getWiedervorlageList(vorgangResource) - .subscribe((wiedervorlagenList) => { - if (!isNull(wiedervorlagenList)) { - this.setWiedervorlagenList(wiedervorlagenList); - subscription.unsubscribe(); - } - }); + const subscription: Subscription = this.repository.getWiedervorlageList(vorgangResource).subscribe((wiedervorlagenList) => { + if (!isNull(wiedervorlagenList)) { + this.setWiedervorlagenList(wiedervorlagenList); + subscription.unsubscribe(); + } + }); } else { this.clearWiedervorlagenList(); } @@ -152,10 +116,6 @@ export class WiedervorlageService implements OnDestroy { ); } - setWiedervorlageListLoading(): void { - this.wiedervorlageList$.next({ ...this.wiedervorlageList$.value, loading: true }); - } - setWiedervorlagenList(wiedervorlagenList: WiedervorlageListResource): void { this.wiedervorlageList$.next(createStateResource(wiedervorlagenList)); } @@ -176,46 +136,34 @@ export class WiedervorlageService implements OnDestroy { private loadAndSetWiedervorlageByUrl(url: ResourceUri): void { this.setWiedervorlageLoading(); - const subscription: Subscription = this.repository - .getWiedervorlage(url) - .subscribe((wiedervorlage) => { - this.wiedervorlage$.next(createStateResource(wiedervorlage)); - subscription.unsubscribe(); - }); + const subscription: Subscription = this.repository.getWiedervorlage(url).subscribe((wiedervorlage) => { + this.wiedervorlage$.next(createStateResource(wiedervorlage)); + subscription.unsubscribe(); + }); } public isNewWiedervorlage(): boolean { - return isEqual( - this.navigationService.getParam(WiedervorlageService.encodedWiedervorlageUriParam), - WiedervorlageRoutes.NEW, - ); + return isEqual(this.navigationService.getParam(WiedervorlageService.encodedWiedervorlageUriParam), WiedervorlageRoutes.NEW); } private listenToNavigation(): void { this.unsubscribe(); - this.subscription = this.navigationService - .urlChanged() - .subscribe((params) => this.onNavigation(params)); + this.subscription = this.navigationService.urlChanged().subscribe((params) => this.onNavigation(params)); } onNavigation(params: Params): void { + this.clearUploadedFiles(); if (this.navigateToVorgangDetailPage(params)) { this.setWiedervorlageListReload(); this.forceWiedervorlageReload(); this.submitInProgress$.next(createEmptyStateResource<CommandResource>()); - this.clearAttachmentList(); } if (NavigationService.isVorgangListPage(params)) { this.clearWiedervorlagenList(); this.submitInProgress$.next(createEmptyStateResource<CommandResource>()); - this.clearAttachmentList(); } } - clearAttachmentList(): void { - this.attachmentListByWiedervorlage = <BinaryFileListByWiedervorlageUri>{}; - } - clearWiedervorlagenList(): void { this.wiedervorlageList$.next(createEmptyStateResource<WiedervorlageListResource>()); } @@ -232,30 +180,20 @@ export class WiedervorlageService implements OnDestroy { } private getWiedervorlageUri(): ResourceUri { - return this.navigationService.getDecodedParam( - WiedervorlageService.encodedWiedervorlageUriParam, - ); + return this.navigationService.getDecodedParam(WiedervorlageService.encodedWiedervorlageUriParam); } setWiedervorlageLoading(): void { this.wiedervorlage$.next({ ...this.wiedervorlage$.value, loading: true }); } - public createWiedervorlage( - wiedervorlage: Wiedervorlage, - ): Observable<StateResource<CommandResource>> { + public createWiedervorlage(wiedervorlage: Wiedervorlage): Observable<StateResource<CommandResource>> { this.setSubmitInProgressLoading(); - return this.createCreateWiedervorlageCommand( - this.wiedervorlageList$.value.resource, - wiedervorlage, - ).pipe( + return this.createCreateWiedervorlageCommand(this.wiedervorlageList$.value.resource, wiedervorlage).pipe( filter((commandStateResource) => !commandStateResource.loading), tap((commandStateResource) => - this.proceedAfterReceiveCommand( - commandStateResource, - this.buildMessage(WiedervorlageMessages.CREATED, wiedervorlage), - ), + this.proceedAfterReceiveCommand(commandStateResource, this.buildMessage(WiedervorlageMessages.CREATED, wiedervorlage)), ), startWith(createEmptyStateResource<CommandResource>(true)), ); @@ -285,10 +223,7 @@ export class WiedervorlageService implements OnDestroy { return this.createSaveWiedervorlageCommand(wiedervorlageResource, wiedervorlage).pipe( filter((commandStateResource) => !commandStateResource.loading), tap((commandStateResource) => - this.proceedAfterReceiveCommand( - commandStateResource, - this.buildMessage(WiedervorlageMessages.SAVED, wiedervorlage), - ), + this.proceedAfterReceiveCommand(commandStateResource, this.buildMessage(WiedervorlageMessages.SAVED, wiedervorlage)), ), startWith(createEmptyStateResource<CommandResource>(true)), ); @@ -313,10 +248,7 @@ export class WiedervorlageService implements OnDestroy { return { order: CommandOrder.EDIT_WIEDERVORLAGE, body: wiedervorlage }; } - proceedAfterReceiveCommand( - commandStateResource: StateResource<CommandResource>, - message: string, - ): void { + proceedAfterReceiveCommand(commandStateResource: StateResource<CommandResource>, message: string): void { this.submitInProgress$.next({ ...this.submitInProgress$.value, resource: commandStateResource.resource, @@ -325,6 +257,7 @@ export class WiedervorlageService implements OnDestroy { this.submitInProgress$.next(createStateResource(commandStateResource.resource)); this.snackbarService.show(commandStateResource.resource, message); this.setWiedervorlageListReload(); + this.clearUploadedFiles(); } else if (hasStateResourceError(commandStateResource)) { this.submitInProgress$.next(createStateResource(commandStateResource.resource)); } @@ -370,6 +303,7 @@ export class WiedervorlageService implements OnDestroy { this.buildMessage(snackBarMessage, this.wiedervorlage$.value.resource), ); commandStateSubj.next(commandStateResource); + this.clearUploadedFiles(); commandSubscription.unsubscribe(); } }); @@ -393,45 +327,6 @@ export class WiedervorlageService implements OnDestroy { } } - public getAttachmentList( - wiedervorlage: WiedervorlageResource, - ): Observable<StateResource<BinaryFileListResource>> { - const uri: ResourceUri = getUrl(wiedervorlage); - - if (isNil(this.attachmentListByWiedervorlage[uri])) { - this.attachmentListByWiedervorlage[uri] = new BehaviorSubject( - createEmptyStateResource<any>(), - ); - } else { - this.attachmentListByWiedervorlage[uri].next(createEmptyStateResource<any>()); - } - - doIfLoadingRequired(this.attachmentListByWiedervorlage[uri].value, () => - this.loadAttachments(wiedervorlage), - ); - - return this.attachmentListByWiedervorlage[uri].asObservable(); - } - - loadAttachments(wiedervorlage: WiedervorlageResource): void { - this.setAttachmentLoading(wiedervorlage); - if (!isNil(this.attachmentSubscription)) this.attachmentSubscription.unsubscribe(); - this.attachmentSubscription = this.binaryFileService - .getFiles(wiedervorlage, WiedervorlageLinkRel.ATTACHMENTS) - .subscribe((fileList) => { - if (fileList.loaded) { - this.attachmentListByWiedervorlage[getUrl(wiedervorlage)].next(fileList); - } - }); - } - - setAttachmentLoading(wiedervorlage: WiedervorlageResource): void { - this.attachmentListByWiedervorlage[getUrl(wiedervorlage)].next({ - ...this.attachmentListByWiedervorlage[getUrl(wiedervorlage)].value, - loading: true, - }); - } - public getSubmitInProgress(): Observable<StateResource<CommandResource>> { return this.submitInProgress$.asObservable(); } @@ -451,4 +346,8 @@ export class WiedervorlageService implements OnDestroy { .getWiedervorlage(decodedUrl) .pipe(map((wiedervorlage) => hasLink(wiedervorlage, WiedervorlageLinkRel.EDIT))); } + + public clearUploadedFiles(): void { + this.binaryFileService.clearUploadedFiles(WIEDERVORLAGE_UPLOADED_ATTACHMENTS); + } } diff --git a/alfa-client/libs/wiedervorlage/src/lib/submit-wiedervorlage-button/submit-wiedervorlage-button.component.spec.ts b/alfa-client/libs/wiedervorlage/src/lib/submit-wiedervorlage-button/submit-wiedervorlage-button.component.spec.ts index 5dfb7fa67a2f94ba32302be9866c2c8c5d0af972..e5090f94ee1d5a9a24dd5e603e10f726e03ef5e0 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/submit-wiedervorlage-button/submit-wiedervorlage-button.component.spec.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/submit-wiedervorlage-button/submit-wiedervorlage-button.component.spec.ts @@ -23,7 +23,7 @@ */ import { CommandResource } from '@alfa-client/command-shared'; import { NavigationService } from '@alfa-client/navigation-shared'; -import { StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { createStateResource, StateResource } from '@alfa-client/tech-shared'; import { mock } from '@alfa-client/test-utils'; import { OzgcloudStrokedButtonWithSpinnerComponent } from '@alfa-client/ui'; import { WiedervorlageService } from '@alfa-client/wiedervorlage-shared'; @@ -89,15 +89,15 @@ describe('SubmitWiedervorlageButtonComponent', () => { expect(component).toBeTruthy(); }); - describe('navigateIfCommandIsDone', () => { + describe('navigate if command is done', () => { it('should navigate on success', () => { - component.navigateIfCommandIsDone(commandWithError); + component._navigateIfCommandIsDone(commandWithError); expect(navigationService.navigateRelativeTo).toHaveBeenCalled(); }); it('should NOT navigate on loading', () => { - component.navigateIfCommandIsDone({ ...commandWithError, loading: true }); + component._navigateIfCommandIsDone({ ...commandWithError, loading: true }); expect(navigationService.navigateRelativeTo).toHaveBeenCalledWith('../../', activatedRoute); }); diff --git a/alfa-client/libs/wiedervorlage/src/lib/submit-wiedervorlage-button/submit-wiedervorlage-button.component.ts b/alfa-client/libs/wiedervorlage/src/lib/submit-wiedervorlage-button/submit-wiedervorlage-button.component.ts index 788e3229c23f99a1fe11f4d0b2c257e0bde00350..2aa76ca8705a9c54fc601aea707527e5e1a3f04e 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/submit-wiedervorlage-button/submit-wiedervorlage-button.component.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/submit-wiedervorlage-button/submit-wiedervorlage-button.component.ts @@ -52,11 +52,11 @@ export class SubmitWiedervorlageButtonComponent { submit(): void { this.submitInProgress$ = this.formService.submit().pipe( filter((command) => !command.loading), - tap((commandWithError) => this.navigateIfCommandIsDone(commandWithError)), + tap((commandWithError) => this._navigateIfCommandIsDone(commandWithError)), ); } - navigateIfCommandIsDone(command: StateResource<CommandResource>): void { + _navigateIfCommandIsDone(command: StateResource<CommandResource>): void { if (!command.loading && isDone(command.resource)) { this.navigateToVorgangDetailPage(); } diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.html b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.html deleted file mode 100644 index 8687a058f25e38cf8042c87506b1e181a4f43e76..0000000000000000000000000000000000000000 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.html +++ /dev/null @@ -1,31 +0,0 @@ -<!-- - - Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<alfa-vertical-binary-file-list - [binaryFileListStateResource]="attachments$ | async" - [deletable]="false" - data-test-id="wiedervorlage-attachment-list" -> -</alfa-vertical-binary-file-list> diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.scss b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.scss deleted file mode 100644 index 7648e3a3481646fbc80529a3a7b59e980b88eb1c..0000000000000000000000000000000000000000 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.scss +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -:host { - display: flex; - margin: 0 -4px; -} - -.attachments { - display: flex; - flex-wrap: wrap; -} diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.spec.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.spec.ts deleted file mode 100644 index a462e5aa51ac4cddeebe82a611ece3bc0ae6ef3c..0000000000000000000000000000000000000000 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ToEmbeddedResourcesPipe } from '@alfa-client/tech-shared'; -import { mock } from '@alfa-client/test-utils'; -import { WiedervorlageService } from '@alfa-client/wiedervorlage-shared'; -import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; -import { WiedervorlageAttachmentListContainerComponent } from './wiedervorlage-attachment-list-container.component'; -import { VerticalBinaryFileListComponent } from '@alfa-client/binary-file'; - -describe('WiedervorlageAttachmentListContainerComponent', () => { - let component: WiedervorlageAttachmentListContainerComponent; - let fixture: ComponentFixture<WiedervorlageAttachmentListContainerComponent>; - - const wiedervorlageService = { ...mock(WiedervorlageService), getAttachmentList: () => of(null) }; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - WiedervorlageAttachmentListContainerComponent, - ToEmbeddedResourcesPipe, - MockComponent(VerticalBinaryFileListComponent), - ], - providers: [ - { - provide: WiedervorlageService, - useValue: wiedervorlageService, - }, - ], - }); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(WiedervorlageAttachmentListContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.ts deleted file mode 100644 index 93f9f0e59fb650f7074b4fe4223cecbf08a86292..0000000000000000000000000000000000000000 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { BinaryFileListResource } from '@alfa-client/binary-file-shared'; -import { StateResource } from '@alfa-client/tech-shared'; -import { WiedervorlageResource, WiedervorlageService } from '@alfa-client/wiedervorlage-shared'; -import { Observable } from 'rxjs'; - -@Component({ - selector: 'alfa-wiedervorlage-attachment-list-container', - templateUrl: './wiedervorlage-attachment-list-container.component.html', - styleUrls: ['./wiedervorlage-attachment-list-container.component.scss'], -}) -export class WiedervorlageAttachmentListContainerComponent implements OnChanges { - @Input() wiedervorlage: WiedervorlageResource; - - attachments$: Observable<StateResource<BinaryFileListResource>>; - - constructor(public service: WiedervorlageService) {} - - ngOnChanges(changes: SimpleChanges): void { - if (changes.wiedervorlage) { - this.loadAttachments(); - } - } - - loadAttachments(): void { - this.attachments$ = this.service.getAttachmentList(this.wiedervorlage); - } -} diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.html b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.html index bdef88d210bb2d4b5e240b70f638a0efeb10c9e3..ee68f8fe3530ede164cfbc82544a053121a95cc8 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.html +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.html @@ -23,10 +23,7 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<div - [attr.data-test-id]="wiedervorlageResource.betreff | convertForDataTest" - class="container text-sm" -> +<div [attr.data-test-id]="wiedervorlageResource.betreff | convertForDataTest" class="container text-sm"> <div class="row"> <alfa-wiedervorlage-status data-test-class="status" @@ -36,20 +33,18 @@ </alfa-wiedervorlage-status> <alfa-link-with-user-name-tooltip-container - *ngIf="wiedervorlageResource | hasLink: linkRel.EDIT; else content" + *ngIf="wiedervorlageResource | hasLink: WiedervorlageLinkRel.EDIT; else content" routerLinkString="wiedervorlage/{{ wiedervorlageResource | toResourceUri }}" [tooltipTemplate]="tooltip" [resource]="wiedervorlageResource" - [linkRel]="linkRel.CREATED_BY" + [linkRel]="WiedervorlageLinkRel.CREATED_BY" data-test-class="link" > <ng-container *ngTemplateOutlet="content"></ng-container> </alfa-link-with-user-name-tooltip-container> <ng-template #content> - <span class="date" data-test-class="frist">{{ - wiedervorlageResource.frist | formatToPrettyDate - }}</span> + <span class="date" data-test-class="frist">{{ wiedervorlageResource.frist | formatToPrettyDate }}</span> <span class="name" data-test-class="betreff">{{ wiedervorlageResource.betreff }}</span> </ng-template> @@ -67,16 +62,17 @@ [tooltipTemplate]="tooltip" [text]="wiedervorlageResource.beschreibung" [resource]="wiedervorlageResource" - [linkRel]="linkRel.CREATED_BY" + [linkRel]="WiedervorlageLinkRel.CREATED_BY" > </alfa-text-with-user-name-tooltip-container> - <alfa-wiedervorlage-attachment-list-container - *ngIf="wiedervorlageResource | hasLink: linkRel.ATTACHMENTS" - data-test-id="wiedervorlage-attachment-list-in-vorgang" - [wiedervorlage]="wiedervorlageResource" - class="attachments" - > - </alfa-wiedervorlage-attachment-list-container> + @if (wiedervorlageResource | hasLink: WiedervorlageLinkRel.ATTACHMENTS) { + <alfa-binary-file-list-container + [resource]="wiedervorlageResource" + [linkRel]="WiedervorlageLinkRel.ATTACHMENTS" + [listOrientation]="BinaryFileListOrientation.VERTICAL" + data-test-id="wiedervorlage-attachment-list-in-vorgang" + ></alfa-binary-file-list-container> + } </div> </div> diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.spec.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.spec.ts index 6673dc39b36123bb4d9bdd8bbb3e06e1e1dc2dc1..f4be1ef8b0b40d801398a057032c8b44020f97c4 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.spec.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.spec.ts @@ -21,20 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - ConvertForDataTestPipe, - FormatToPrettyDatePipe, - HasLinkPipe, - ToResourceUriPipe, - ToTrafficLightTooltipPipe, - formatFullDate, -} from '@alfa-client/tech-shared'; +import { ConvertForDataTestPipe, formatFullDate, FormatToPrettyDatePipe, HasLinkPipe, ToResourceUriPipe, ToTrafficLightTooltipPipe, } from '@alfa-client/tech-shared'; import { dispatchEventFromFixture } from '@alfa-client/test-utils'; import { ExpansionPanelComponent } from '@alfa-client/ui'; -import { - LinkWithUserNameTooltipContainerComponent, - TextWithUserNameTooltipContainerComponent, -} from '@alfa-client/user-profile'; +import { LinkWithUserNameTooltipContainerComponent, TextWithUserNameTooltipContainerComponent } from '@alfa-client/user-profile'; import { WiedervorlageResource } from '@alfa-client/wiedervorlage-shared'; import { registerLocaleData } from '@angular/common'; import localeDe from '@angular/common/locales/de'; @@ -48,7 +38,6 @@ import { TooltipDirective } from '@ods/system'; import { createWiedervorlageResource } from 'libs/wiedervorlage-shared/test/wiedervorlage'; import { MockComponent, MockDirective } from 'ng-mocks'; import { WiedervorlageStatusComponent } from '../../../wiedervorlage-status/wiedervorlage-status.component'; -import { WiedervorlageAttachmentListContainerComponent } from './wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component'; import { WiedervorlageInVorgangExpandButtonComponent } from './wiedervorlage-in-vorgang-expand-button/wiedervorlage-in-vorgang-expand-button.component'; import { WiedervorlageInVorgangComponent } from './wiedervorlage-in-vorgang.component'; @@ -74,7 +63,6 @@ describe('WiedervorlageInVorgangComponent', () => { HasLinkPipe, MockComponent(ExpansionPanelComponent), MockComponent(WiedervorlageStatusComponent), - MockComponent(WiedervorlageAttachmentListContainerComponent), MockComponent(WiedervorlageInVorgangExpandButtonComponent), MockComponent(LinkWithUserNameTooltipContainerComponent), MockComponent(TextWithUserNameTooltipContainerComponent), diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.ts index ee5f2a95c847513473d46821392820ff39650081..727acc5acd01ca8aac2eacb639f4780ecf5db9b3 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.ts @@ -21,13 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input, OnInit } from '@angular/core'; import { formatFullDate, replacePlaceholders } from '@alfa-client/tech-shared'; -import { - WiedervorlageLinkRel, - WiedervorlageMessages, - WiedervorlageResource, -} from '@alfa-client/wiedervorlage-shared'; +import { WiedervorlageLinkRel, WiedervorlageMessages, WiedervorlageResource } from '@alfa-client/wiedervorlage-shared'; +import { Component, Input, OnInit } from '@angular/core'; +import { BinaryFileListOrientation } from '../../../../../../binary-file/src/lib/directive/binary-file-list-orientation/binary-file-list-orientation.directive'; @Component({ selector: 'alfa-wiedervorlage-in-vorgang', @@ -40,7 +37,8 @@ export class WiedervorlageInVorgangComponent implements OnInit { tooltip: string; expanded: boolean; - readonly linkRel = WiedervorlageLinkRel; + public readonly WiedervorlageLinkRel = WiedervorlageLinkRel; + public readonly BinaryFileListOrientation = BinaryFileListOrientation; ngOnInit(): void { this.tooltip = this.formatTooltip(); diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.html b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.html index 014b5ba17d0289e7b339f6409cf0e32acb7b1194..d7c191e5c0ceca89a1123b107fa04c210d4a4938 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.html +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.html @@ -45,14 +45,14 @@ > </ozgcloud-date-editor> - <alfa-binary-file-attachment-container - data-test-id="wiedervorlage-attachment-list" - [existFiles]="attachments$ | async" - [formArrayName]="formServiceClass.FIELD_ATTACHMENTS" - [uploadStateResource]="wiedervorlageListStateResource" - [linkRelUploadAttachment]="wiedervorlageListLinkrel.UPLOAD_FILE" - > - </alfa-binary-file-attachment-container> + <ods-multi-file-upload + [filesFormFieldName]="formServiceClass.FIELD_ATTACHMENTS" + [fileUploadType]="WIEDERVORLAGE_UPLOADED_ATTACHMENTS" + [uploadResource]="wiedervorlageListStateResource.resource" + [uploadLinkRelation]="WiedervorlageListLinkRel.UPLOAD_FILE" + [filesResource]="wiedervorlage" + [filesLinkRelation]="WiedervorlageLinkRel.ATTACHMENTS" + ></ods-multi-file-upload> <alfa-submit-wiedervorlage-button class="submit-button"></alfa-submit-wiedervorlage-button> </form> diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.spec.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.spec.ts index f7dfb80e9b4ab4478fc2063574cda7411e15498d..014466c7f15afb5a0ca42498b5f7f4db16f7f36e 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.spec.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.spec.ts @@ -22,17 +22,16 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BinaryFileAttachmentContainerComponent } from '@alfa-client/binary-file'; -import { BinaryFileListResource } from '@alfa-client/binary-file-shared'; +import { createStateResource, StateResource } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { DateEditorComponent, OzgcloudTextEditorComponent, TextAreaEditorComponent } from '@alfa-client/ui'; -import { WiedervorlageLinkRel, WiedervorlageResource, WiedervorlageService } from '@alfa-client/wiedervorlage-shared'; +import { WiedervorlageListResource, WiedervorlageResource, WiedervorlageService } from '@alfa-client/wiedervorlage-shared'; import { SimpleChanges } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule, UntypedFormBuilder } from '@angular/forms'; -import { createBinaryFileListResource } from 'libs/binary-file-shared/test/binary-file'; -import { createWiedervorlageResource } from 'libs/wiedervorlage-shared/test/wiedervorlage'; +import { createWiedervorlageListResource, createWiedervorlageResource } from 'libs/wiedervorlage-shared/test/wiedervorlage'; import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; +import { MultiFileUploadComponent } from '../../../../../../binary-file/src/lib/multi-file-upload/multi-file-upload.component'; import { SubmitWiedervorlageButtonComponent } from '../../../submit-wiedervorlage-button/submit-wiedervorlage-button.component'; import { WiedervorlageFormComponent } from './wiedervorlage-form.component'; import { WiedervorlageFormService } from './wiedervorlage.formservice'; @@ -44,6 +43,9 @@ describe('WiedervorlageFormComponent', () => { const formService = new WiedervorlageFormService(new UntypedFormBuilder(), useFromMock(mock(WiedervorlageService))); const wiedervorlageService: Mock<WiedervorlageService> = mock(WiedervorlageService); const wiedervorlage: WiedervorlageResource = createWiedervorlageResource(); + const wiedervorlaeListStateResource: StateResource<WiedervorlageListResource> = createStateResource( + createWiedervorlageListResource(), + ); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -54,6 +56,7 @@ describe('WiedervorlageFormComponent', () => { MockComponent(TextAreaEditorComponent), MockComponent(BinaryFileAttachmentContainerComponent), MockComponent(SubmitWiedervorlageButtonComponent), + MockComponent(MultiFileUploadComponent), ], imports: [ReactiveFormsModule], providers: [ @@ -74,6 +77,7 @@ describe('WiedervorlageFormComponent', () => { fixture = TestBed.createComponent(WiedervorlageFormComponent); component = fixture.componentInstance; + component.wiedervorlageListStateResource = wiedervorlaeListStateResource; fixture.detectChanges(); }); @@ -87,7 +91,6 @@ describe('WiedervorlageFormComponent', () => { beforeEach(() => { component.wiedervorlage = wiedervorlage; component.patchWiedervorlage = jest.fn(); - component.updateAttachments = jest.fn(); }); it('should call patchWiedervorlage', () => { @@ -95,37 +98,6 @@ describe('WiedervorlageFormComponent', () => { expect(component.patchWiedervorlage).toHaveBeenCalled(); }); - - it('should call updateAttachments', () => { - component.ngOnChanges(simpleChanges); - - expect(component.updateAttachments).toHaveBeenCalled(); - }); - }); - - describe('updateAttachments', () => { - const binaryFileListResource: BinaryFileListResource = createBinaryFileListResource(); - - beforeEach(() => { - wiedervorlageService.getAttachmentList.mockReturnValue(of(binaryFileListResource)); - }); - - it('should call wiedervorlage service to get attachments if links exists', () => { - const wiedervorlageWithAttachments: WiedervorlageResource = createWiedervorlageResource([WiedervorlageLinkRel.ATTACHMENTS]); - component.wiedervorlage = wiedervorlageWithAttachments; - - component.updateAttachments(); - - expect(wiedervorlageService.getAttachmentList).toHaveBeenCalledWith(wiedervorlageWithAttachments); - }); - - it('should NOT call wiedervorlage service if link is not present', () => { - component.wiedervorlage = wiedervorlage; - - component.updateAttachments(); - - expect(wiedervorlageService.getAttachmentList).not.toHaveBeenCalledWith(wiedervorlage); - }); }); describe('patch wiedervorlage', () => { diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.ts index 06cc5576e632fbde55113894af5e653247ac1780..24f0d1af790e44119b13eb4fd6d5a4d9acb58f0d 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-form/wiedervorlage-form.component.ts @@ -21,20 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { BinaryFileListLinkRel, BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { StateResource, getEmbeddedResources, isNotNil } from '@alfa-client/tech-shared'; -import { - WiedervorlageLinkRel, - WiedervorlageListLinkRel, - WiedervorlageListResource, - WiedervorlageResource, - WiedervorlageService, -} from '@alfa-client/wiedervorlage-shared'; -import { hasLink } from '@ngxp/rest'; +import { isNotNil, StateResource } from '@alfa-client/tech-shared'; +import { WIEDERVORLAGE_UPLOADED_ATTACHMENTS, WiedervorlageLinkRel, WiedervorlageListLinkRel, WiedervorlageListResource, WiedervorlageResource, } from '@alfa-client/wiedervorlage-shared'; +import { Component, inject, Input, OnChanges, SimpleChanges } from '@angular/core'; import { isNull } from 'lodash-es'; -import { Observable, of } from 'rxjs'; -import { map } from 'rxjs/operators'; import { WiedervorlageFormService } from './wiedervorlage.formservice'; //TODO Container Component zwischenschalten @@ -47,39 +37,19 @@ export class WiedervorlageFormComponent implements OnChanges { @Input() wiedervorlageListStateResource: StateResource<WiedervorlageListResource>; @Input() wiedervorlage: WiedervorlageResource; - attachments$: Observable<BinaryFileResource[]> = of([]); + public readonly formService = inject(WiedervorlageFormService); - readonly formServiceClass = WiedervorlageFormService; - readonly wiedervorlageListLinkrel = WiedervorlageListLinkRel; - - constructor( - public formService: WiedervorlageFormService, - private wiedervorlageService: WiedervorlageService, - ) {} + public readonly formServiceClass = WiedervorlageFormService; + public readonly WiedervorlageListLinkRel = WiedervorlageListLinkRel; + public readonly WiedervorlageLinkRel = WiedervorlageLinkRel; + public readonly WIEDERVORLAGE_UPLOADED_ATTACHMENTS = WIEDERVORLAGE_UPLOADED_ATTACHMENTS; ngOnChanges(changes: SimpleChanges): void { if (changes.wiedervorlage && isNotNil(this.wiedervorlage)) { - this.updateAttachments(); this.patchWiedervorlage(); } } - updateAttachments(): void { - if (hasLink(this.wiedervorlage, WiedervorlageLinkRel.ATTACHMENTS)) { - this.attachments$ = this.wiedervorlageService - .getAttachmentList(this.wiedervorlage) - .pipe( - map((attachmentList) => - getEmbeddedResources<BinaryFileResource>( - attachmentList, - BinaryFileListLinkRel.FILE_LIST, - ), - ), - ); - } else { - this.attachments$ = of([]); - } - } patchWiedervorlage(): void { if (!isNull(this.wiedervorlage)) { this.formService.patch(this.wiedervorlage); diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-page.component.html b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-page.component.html index 8f0c3d3c5dfb5e59a191e8e18c725e098376b2fe..99cc6d70f2db39cd1ab3fb16016af9f8f07288ac 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-page.component.html +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-page.component.html @@ -24,31 +24,19 @@ --> <ozgcloud-spinner [stateResource]="wiedervorlageStateResource"> - <ozgcloud-subnavigation - data-test-id="subnavigation-wiedervorlage" - class="mat-typography mat-app-background" - > - <alfa-wiedervorlage-action-buttons [wiedervorlage]="wiedervorlageStateResource.resource"> - </alfa-wiedervorlage-action-buttons> + <ozgcloud-subnavigation data-test-id="subnavigation-wiedervorlage" class="mat-typography mat-app-background"> + <alfa-wiedervorlage-action-buttons [wiedervorlage]="wiedervorlageStateResource.resource"> </alfa-wiedervorlage-action-buttons> </ozgcloud-subnavigation> <div class="l-scroll-area--full"> <div class="wrapper grow"> - <h1 class="text-lg font-medium">Wiedervorlage</h1> + <h1 class="text-lg font-medium" data-test-id="wiedervorlage-headline">Wiedervorlage</h1> <alfa-wiedervorlage-breadcrumb-container [wiedervorlage]="wiedervorlageStateResource.resource" ></alfa-wiedervorlage-breadcrumb-container> - <alfa-wiedervorlage-status - class="status" - [wiedervorlageResource]="wiedervorlageStateResource.resource" - [diameter]="16" - > - <span>{{ - wiedervorlageStateResource.resource && wiedervorlageStateResource.resource.done ? - 'erledigt' - : 'offen' - }}</span> + <alfa-wiedervorlage-status class="status" [wiedervorlageResource]="wiedervorlageStateResource.resource" [diameter]="16"> + <span>{{ wiedervorlageStateResource.resource && wiedervorlageStateResource.resource.done ? 'erledigt' : 'offen' }}</span> </alfa-wiedervorlage-status> <alfa-wiedervorlage-form diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage.module.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage.module.ts index 2057849d9d85c3365877c9d58fd022a8137029dc..7fe2f1babb5a64816093254a75dd52cfe1b6fb3e 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage.module.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage.module.ts @@ -22,26 +22,8 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BinaryFileModule } from '@alfa-client/binary-file'; -import { - ConvertForDataTestPipe, - FormatToPrettyDatePipe, - HasLinkPipe, - ToEmbeddedResourcesPipe, - ToResourceUriPipe, - ToTrafficLightPipe, - ToTrafficLightTooltipPipe, -} from '@alfa-client/tech-shared'; -import { - BackButtonComponent, - DateEditorComponent, - ExpansionPanelComponent, - IconButtonWithSpinnerComponent, - OzgcloudStrokedButtonWithSpinnerComponent, - OzgcloudTextEditorComponent, - SpinnerComponent, - SubnavigationComponent, - TextAreaEditorComponent, -} from '@alfa-client/ui'; +import { ConvertForDataTestPipe, FormatToPrettyDatePipe, HasLinkPipe, ToEmbeddedResourcesPipe, ToResourceUriPipe, ToTrafficLightPipe, ToTrafficLightTooltipPipe, } from '@alfa-client/tech-shared'; +import { BackButtonComponent, DateEditorComponent, ExpansionPanelComponent, IconButtonWithSpinnerComponent, OzgcloudStrokedButtonWithSpinnerComponent, OzgcloudTextEditorComponent, SpinnerComponent, SubnavigationComponent, TextAreaEditorComponent, } from '@alfa-client/ui'; import { UserProfileModule } from '@alfa-client/user-profile'; import { VorgangSharedModule } from '@alfa-client/vorgang-shared'; import { VorgangSharedUiModule } from '@alfa-client/vorgang-shared-ui'; @@ -52,20 +34,14 @@ import { MatIcon } from '@angular/material/icon'; import { MatTooltip } from '@angular/material/tooltip'; import { RouterModule, Routes } from '@angular/router'; import { ButtonWithSpinnerComponent } from '@ods/component'; -import { - CheckCircleIconComponent, - PlusIconComponent, - SaveIconComponent, - TooltipDirective, - UpdateIconComponent, -} from '@ods/system'; +import { CheckCircleIconComponent, PlusIconComponent, SaveIconComponent, TooltipDirective, UpdateIconComponent, } from '@ods/system'; +import { MultiFileUploadComponent } from '../../../binary-file/src/lib/multi-file-upload/multi-file-upload.component'; import { CreateWiedervorlageButtonContainerComponent } from './create-wiedervorlage-button-container/create-wiedervorlage-button-container.component'; import { ErledigenButtonContainerComponent } from './erledigen-button-container/erledigen-button-container.component'; import { SubmitWiedervorlageButtonComponent } from './submit-wiedervorlage-button/submit-wiedervorlage-button.component'; import { WiedereroeffnenButtonContainerComponent } from './wiedereroeffnen-button-container/wiedereroeffnen-button-container.component'; import { WiedervorlageListInVorgangContainerComponent } from './wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang-container.component'; import { WiedervorlageCreateButtonComponent } from './wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-create-button/wiedervorlage-create-button.component'; -import { WiedervorlageAttachmentListContainerComponent } from './wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component'; import { WiedervorlageInVorgangExpandButtonComponent } from './wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang-expand-button/wiedervorlage-in-vorgang-expand-button.component'; import { WiedervorlageInVorgangComponent } from './wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component'; import { WiedervorlageListInVorgangComponent } from './wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-list-in-vorgang.component'; @@ -121,6 +97,7 @@ const routes: Routes = [ PlusIconComponent, CheckCircleIconComponent, SaveIconComponent, + MultiFileUploadComponent, ], declarations: [ WiedervorlagePageComponent, @@ -138,7 +115,6 @@ const routes: Routes = [ WiedereroeffnenButtonContainerComponent, ErledigenButtonContainerComponent, CreateWiedervorlageButtonContainerComponent, - WiedervorlageAttachmentListContainerComponent, WiedervorlageBreadcrumbContainerComponent, WiedervorlageInVorgangExpandButtonComponent, ], diff --git a/alfa-client/libs/zustaendige-stelle-shared/src/lib/externe-fachstelle/externe-fachstelle.service.spec.ts b/alfa-client/libs/zustaendige-stelle-shared/src/lib/externe-fachstelle/externe-fachstelle.service.spec.ts index 99231c0db52685014185c54da0ccff7f83393525..3c25ba4614aa332c0c79bddb04206cbb740ee7fc 100644 --- a/alfa-client/libs/zustaendige-stelle-shared/src/lib/externe-fachstelle/externe-fachstelle.service.spec.ts +++ b/alfa-client/libs/zustaendige-stelle-shared/src/lib/externe-fachstelle/externe-fachstelle.service.spec.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { EMPTY_ARRAY, StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { StateResource, createStateResource } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { faker } from '@faker-js/faker'; import { InstantSearchResult } from '@ods/system'; @@ -111,7 +111,7 @@ describe('ExterneFachstelleService', () => { createStateResource(createEmptyListResource<ExterneFachstelleListResource>()), ); - expect(result).toEqual(EMPTY_ARRAY); + expect(result).toEqual([]); }); }); diff --git a/alfa-client/libs/zustaendige-stelle-shared/src/lib/externe-fachstelle/externe-fachstelle.service.ts b/alfa-client/libs/zustaendige-stelle-shared/src/lib/externe-fachstelle/externe-fachstelle.service.ts index 6c9493c8266927d9a99517855ee5f1568ae129ed..50e3c315d376590df9cf2c20be7e9b68fa29213a 100644 --- a/alfa-client/libs/zustaendige-stelle-shared/src/lib/externe-fachstelle/externe-fachstelle.service.ts +++ b/alfa-client/libs/zustaendige-stelle-shared/src/lib/externe-fachstelle/externe-fachstelle.service.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { EMPTY_ARRAY, getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; +import { getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; import { Injectable } from '@angular/core'; import { InstantSearchResult } from '@ods/system'; import { isNull } from 'lodash-es'; @@ -60,7 +60,7 @@ export class ExterneFachstelleService implements ZustaendigeStelleService<Extern externeFachstelleStateListResource, ExterneFachstelleListLinkRel.EXTERNE_FACHSTELLE_LIST, ); - return isNull(resources) ? EMPTY_ARRAY : resources; + return isNull(resources) ? [] : resources; } mapToInstantSearchResult(externeFachstelle: ExterneFachstelleResource): InstantSearchResult<ExterneFachstelleResource> { diff --git a/alfa-client/libs/zustaendige-stelle-shared/src/lib/organisations-einheit/organisations-einheit.service.spec.ts b/alfa-client/libs/zustaendige-stelle-shared/src/lib/organisations-einheit/organisations-einheit.service.spec.ts index 87e9bbd0b160907d93887c58f48af63d576c0fd5..6c994b351bfe3c6dc4212a5b52f807c5ff536e47 100644 --- a/alfa-client/libs/zustaendige-stelle-shared/src/lib/organisations-einheit/organisations-einheit.service.spec.ts +++ b/alfa-client/libs/zustaendige-stelle-shared/src/lib/organisations-einheit/organisations-einheit.service.spec.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { EMPTY_ARRAY, ResourceSearchService, StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { ResourceSearchService, StateResource, createStateResource } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { faker } from '@faker-js/faker'; import { Resource } from '@ngxp/rest'; @@ -109,7 +109,7 @@ describe('OrganisationsEinheitService', () => { createStateResource(createEmptyListResource<OrganisationsEinheitListResource>()), ); - expect(result).toEqual(EMPTY_ARRAY); + expect(result).toEqual([]); }); }); diff --git a/alfa-client/libs/zustaendige-stelle-shared/src/lib/organisations-einheit/organisations-einheit.service.ts b/alfa-client/libs/zustaendige-stelle-shared/src/lib/organisations-einheit/organisations-einheit.service.ts index 578a5ad04f1a4800ac0d2133c2403818d23acb5b..b3c4fe94f7b2e14a9c69cfb8ecf550579e33bc5c 100644 --- a/alfa-client/libs/zustaendige-stelle-shared/src/lib/organisations-einheit/organisations-einheit.service.ts +++ b/alfa-client/libs/zustaendige-stelle-shared/src/lib/organisations-einheit/organisations-einheit.service.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { EMPTY_ARRAY, getEmbeddedResources, ResourceSearchService, StateResource } from '@alfa-client/tech-shared'; +import { getEmbeddedResources, ResourceSearchService, StateResource } from '@alfa-client/tech-shared'; import { Resource } from '@ngxp/rest'; import { InstantSearchResult } from '@ods/system'; import { isNull } from 'lodash-es'; @@ -64,7 +64,7 @@ export class OrganisationsEinheitService implements ZustaendigeStelleService<Org organisationsEinheitStateListResource, OrganisationsEinheitListLinkRel.ORGANISATIONS_EINHEIT_HEADER_LIST, ); - return isNull(resources) ? EMPTY_ARRAY : resources; + return isNull(resources) ? [] : resources; } mapToInstantSearchResult(zustaendigeStelle: OrganisationsEinheitResource): InstantSearchResult<OrganisationsEinheitResource> { diff --git a/alfa-client/package.json b/alfa-client/package.json index 56791618d048e514e34de89a49233389e0a988c4..d6a7bffddc6984305f4bc77149fa653d2083dfd3 100644 --- a/alfa-client/package.json +++ b/alfa-client/package.json @@ -175,4 +175,4 @@ "@rollup/rollup-linux-x64-gnu": "*" }, "packageManager": "pnpm@9.15.0" -} \ No newline at end of file +} diff --git a/alfa-client/pnpm-lock.yaml b/alfa-client/pnpm-lock.yaml index f875d36ba7bae2db40b66c5570722a489edb5300..c447ee766cd2cdc982a9fcdb64d528ddd73d27e5 100644 --- a/alfa-client/pnpm-lock.yaml +++ b/alfa-client/pnpm-lock.yaml @@ -4016,6 +4016,10 @@ packages: resolution: {integrity: sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.24.0': + resolution: {integrity: sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/type-utils@7.18.0': resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -4043,6 +4047,10 @@ packages: resolution: {integrity: sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.24.0': + resolution: {integrity: sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@7.18.0': resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -4061,6 +4069,12 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.24.0': + resolution: {integrity: sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.8.0' + '@typescript-eslint/utils@7.18.0': resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} engines: {node: ^18.18.0 || >=20.0.0} @@ -4073,6 +4087,13 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/utils@8.24.0': + resolution: {integrity: sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + '@typescript-eslint/visitor-keys@7.18.0': resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -4081,8 +4102,12 @@ packages: resolution: {integrity: sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.2.1': - resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + '@typescript-eslint/visitor-keys@8.24.0': + resolution: {integrity: sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} '@vitejs/plugin-basic-ssl@1.1.0': resolution: {integrity: sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==} @@ -5855,6 +5880,10 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -9690,6 +9719,12 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-api-utils@2.0.1: + resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -14290,7 +14325,7 @@ snapshots: '@nx/js': 19.8.8(@babel/traverse@7.25.9)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.13))(@swc/types@0.1.17)(typescript@5.5.4))(@swc/core@1.5.29(@swc/helpers@0.5.13))(@types/node@20.17.6)(nx@19.8.8(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.13))(@swc/types@0.1.17)(typescript@5.5.4))(@swc/core@1.5.29(@swc/helpers@0.5.13)))(typescript@5.5.4) '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/type-utils': 8.13.0(eslint@8.57.0)(typescript@5.5.4) - '@typescript-eslint/utils': 8.13.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/utils': 8.24.0(eslint@8.57.0)(typescript@5.5.4) chalk: 4.1.2 confusing-browser-globals: 1.0.11 globals: 15.12.0 @@ -15946,6 +15981,11 @@ snapshots: '@typescript-eslint/types': 8.13.0 '@typescript-eslint/visitor-keys': 8.13.0 + '@typescript-eslint/scope-manager@8.24.0': + dependencies: + '@typescript-eslint/types': 8.24.0 + '@typescript-eslint/visitor-keys': 8.24.0 + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) @@ -15974,6 +16014,8 @@ snapshots: '@typescript-eslint/types@8.13.0': {} + '@typescript-eslint/types@8.24.0': {} + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': dependencies: '@typescript-eslint/types': 7.18.0 @@ -16004,6 +16046,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.24.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 8.24.0 + '@typescript-eslint/visitor-keys': 8.24.0 + debug: 4.3.7(supports-color@8.1.1) + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 2.0.1(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) @@ -16026,6 +16082,17 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@8.24.0(eslint@8.57.0)(typescript@5.5.4)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) + '@typescript-eslint/scope-manager': 8.24.0 + '@typescript-eslint/types': 8.24.0 + '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.5.4) + eslint: 8.57.0 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@7.18.0': dependencies: '@typescript-eslint/types': 7.18.0 @@ -16036,7 +16103,12 @@ snapshots: '@typescript-eslint/types': 8.13.0 eslint-visitor-keys: 3.4.3 - '@ungap/structured-clone@1.2.1': {} + '@typescript-eslint/visitor-keys@8.24.0': + dependencies: + '@typescript-eslint/types': 8.24.0 + eslint-visitor-keys: 4.2.0 + + '@ungap/structured-clone@1.3.0': {} '@vitejs/plugin-basic-ssl@1.1.0(vite@5.4.6(@types/node@20.17.6)(less@4.2.0)(sass@1.77.6)(stylus@0.59.0)(terser@5.31.6))': dependencies: @@ -18141,6 +18213,8 @@ snapshots: eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.0: {} + eslint@8.57.0: dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) @@ -18150,7 +18224,7 @@ snapshots: '@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.1 + '@ungap/structured-clone': 1.3.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -22722,6 +22796,10 @@ snapshots: dependencies: typescript: 5.5.4 + ts-api-utils@2.0.1(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + ts-dedent@2.2.0: {} ts-interface-checker@0.1.13: {} diff --git a/alfa-client/pom.xml b/alfa-client/pom.xml deleted file mode 100644 index 017bc5fd3f5d2419879bc3a31934f0f0612d1473..0000000000000000000000000000000000000000 --- a/alfa-client/pom.xml +++ /dev/null @@ -1,135 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - - Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - - <parent> - <groupId>de.ozgcloud.alfa</groupId> - <artifactId>alfa</artifactId> - <version>2.18.0-SNAPSHOT</version> - </parent> - - <modelVersion>4.0.0</modelVersion> - <artifactId>alfa-client</artifactId> - <name>Alfa Client</name> - <packaging>pom</packaging> - <inceptionYear>2020</inceptionYear> - - <build> - <plugins> - <plugin> - <artifactId>maven-clean-plugin</artifactId> - <configuration> - <filesets> - <fileset> - <directory>dist</directory> - <includes> - <include>*</include> - <include>**/*</include> - </includes> - </fileset> - </filesets> - </configuration> - </plugin> - - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>exec-maven-plugin</artifactId> - <version>3.0.0</version> - <executions> - <execution> - <id>test-application</id> - <phase>test</phase> - <configuration> - <workingDirectory>./</workingDirectory> - <executable>pnpm</executable> - <arguments> - <argument>run</argument> - <argument>test</argument> - </arguments> - <skip>${skipTests}</skip> - </configuration> - <goals> - <goal>exec</goal> - </goals> - </execution> - <execution> - <id>build-application</id> - <phase>compile</phase> - <configuration> - <workingDirectory>./</workingDirectory> - <executable>pnpm</executable> - <arguments> - <argument>run</argument> - <argument>ci-build</argument> - </arguments> - </configuration> - <goals> - <goal>exec</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> - - <profiles> - <profile> - <activation> - <property> - <name>!skipNpmInstall</name> - </property> - </activation> - <id>npmInstall</id> - <build> - <plugins> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>exec-maven-plugin</artifactId> - <version>3.0.0</version> - <executions> - <execution> - <id>install-dependencies</id> - <phase>generate-sources</phase> - <configuration> - <workingDirectory>./</workingDirectory> - <executable>pnpm</executable> - <arguments> - <argument>install</argument> - </arguments> - </configuration> - <goals> - <goal>exec</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> - </profile> - </profiles> - -</project> diff --git a/alfa-client/tsconfig.base.json b/alfa-client/tsconfig.base.json index f6bb4c8b2657d8acef026b997d32794971be5656..615131502b18ad4b0d693d9d2baeed734f62c4ce 100644 --- a/alfa-client/tsconfig.base.json +++ b/alfa-client/tsconfig.base.json @@ -78,7 +78,8 @@ "@alfa-client/zustaendige-stelle-shared": ["libs/zustaendige-stelle-shared/src/index.ts"], "@authentication": ["libs/authentication/src/index.ts"], "@ods/component": ["libs/design-component/src/index.ts"], - "@ods/system": ["libs/design-system/src/index.ts"] + "@ods/system": ["libs/design-system/src/index.ts"], + "admin-user-profile": ["libs/admin/admin-user-profile/src/index.ts"] } }, "exclude": ["node_modules", "tmp"] diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 1becad5631bbd10ec78b3b254a89d26b82c1ffcd..0000000000000000000000000000000000000000 --- a/pom.xml +++ /dev/null @@ -1,172 +0,0 @@ -<?xml version="1.0"?> -<!-- - - Copyright (C) 2020 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>de.ozgcloud.common</groupId> - <artifactId>ozgcloud-common-parent</artifactId> - <version>4.6.0</version> - </parent> - - <groupId>de.ozgcloud.alfa</groupId> - <artifactId>alfa</artifactId> - <version>2.19.0</version> - <name>Alfa Parent</name> - <packaging>pom</packaging> - <inceptionYear>2020</inceptionYear> - - <modules> - <module>alfa-client</module> - <module>alfa-server</module> - <module>alfa-service</module> - </modules> - - <properties> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - - <vorgang-manager.version>2.19.0-SNAPSHOT</vorgang-manager.version> - <nachrichten-manager.version>2.11.0</nachrichten-manager.version> - <ozgcloud-common-pdf.version>3.0.1</ozgcloud-common-pdf.version> - <user-manager.version>2.8.0</user-manager.version> - <zufi-manager.version>1.5.0</zufi-manager.version> - <collaboration-manager.version>0.5.0</collaboration-manager.version> - <archive-manager.version>0.1.0-SNAPSHOT</archive-manager.version> - <document-manager.version>1.1.0</document-manager.version> - <spring-cloud-config-client.version>4.1.3</spring-cloud-config-client.version> - - <!-- TODO: die Version über ozgcloud-common ziehen --> - <jjwt.version>0.12.6</jjwt.version> - </properties> - - <build> - <pluginManagement> - <plugins> - <plugin> - <groupId>com.mycila</groupId> - <artifactId>license-maven-plugin</artifactId> - </plugin> - </plugins> - </pluginManagement> - </build> - - <dependencyManagement> - <dependencies> - <dependency> - <groupId>de.ozgcloud.vorgang</groupId> - <artifactId>vorgang-manager-interface</artifactId> - <version>${vorgang-manager.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.zufi</groupId> - <artifactId>zufi-manager-interface</artifactId> - <version>${zufi-manager.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.nachrichten</groupId> - <artifactId>nachrichten-manager-interface</artifactId> - <version>${nachrichten-manager.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.vorgang</groupId> - <artifactId>vorgang-manager-utils</artifactId> - <version>${vorgang-manager.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.common</groupId> - <artifactId>ozgcloud-common-pdf</artifactId> - <version>${ozgcloud-common-pdf.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.user</groupId> - <artifactId>user-manager-interface</artifactId> - <version>${user-manager.version}</version> - <exclusions> - <exclusion> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-grpc</artifactId> - </exclusion> - <exclusion> - <groupId>org.jboss.slf4j</groupId> - <artifactId>slf4j-jboss-logmanager</artifactId> - </exclusion> - </exclusions> - </dependency> - <dependency> - <groupId>de.ozgcloud.collaboration</groupId> - <artifactId>collaboration-manager-interface</artifactId> - <version>${collaboration-manager.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.archive</groupId> - <artifactId>archive-manager-interface</artifactId> - <version>${archive-manager.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.document</groupId> - <artifactId>document-manager-interface</artifactId> - <version>${document-manager.version}</version> - </dependency> - - <dependency> - <groupId>io.jsonwebtoken</groupId> - <artifactId>jjwt-api</artifactId> - <version>${jjwt.version}</version> - </dependency> - <dependency> - <groupId>io.jsonwebtoken</groupId> - <artifactId>jjwt-impl</artifactId> - <version>${jjwt.version}</version> - </dependency> - <dependency> - <groupId>io.jsonwebtoken</groupId> - <artifactId>jjwt-jackson</artifactId> - <version>${jjwt.version}</version> - <scope>runtime</scope> - </dependency> - <dependency> - <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-starter-config</artifactId> - <version>${spring-cloud-config-client.version}</version> - </dependency> - </dependencies> - </dependencyManagement> - - <distributionManagement> - <repository> - <id>ozg-nexus</id> - <name>ozg-releases</name> - <url>https://nexus.ozg-sh.de/repository/ozg-releases/</url> - </repository> - <snapshotRepository> - <id>ozg-snapshots-nexus</id> - <name>ozg-snapshots</name> - <url>https://nexus.ozg-sh.de/repository/ozg-snapshots/</url> - </snapshotRepository> - </distributionManagement> -</project> diff --git a/release-erstellen.sh b/release-erstellen.sh deleted file mode 100755 index 182ce07649b687a7ac150227283ae58547da58ec..0000000000000000000000000000000000000000 --- a/release-erstellen.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den -# Ministerpräsidenten des Landes Schleswig-Holstein -# Staatskanzlei -# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung -# -# Lizenziert unter der EUPL, Version 1.2 oder - sobald -# diese von der Europäischen Kommission genehmigt wurden - -# Folgeversionen der EUPL ("Lizenz"); -# Sie dürfen dieses Werk ausschließlich gemäß -# dieser Lizenz nutzen. -# Eine Kopie der Lizenz finden Sie hier: -# -# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 -# -# Sofern nicht durch anwendbare Rechtsvorschriften -# gefordert oder in schriftlicher Form vereinbart, wird -# die unter der Lizenz verbreitete Software "so wie sie -# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - -# ausdrücklich oder stillschweigend - verbreitet. -# Die sprachspezifischen Genehmigungen und Beschränkungen -# unter der Lizenz sind dem Lizenztext zu entnehmen. -# - - -if [ "$#" -ne 1 ]; then - echo "Aufruf: ozg-release-erstellen.sh JA" - echo "Als Parameter bitte 'JA' eintragen zur Sicherheit" - exit 1 -fi - - -## alle -SNAPSHOT in pom.xmls entfernen -#find . -name pom.xml -exec sed -i 's/-SNAPSHOT//g' {} + -SED_PARAMS="-i" -if [[ "$OSTYPE" =~ ^darwin ]]; then - SED_PARAMS="$SED_PARAMS '' -e" -fi -find . -name pom.xml -exec sed $SED_PARAMS 's/-SNAPSHOT//g' {} + - -## release version auslesen -NEWVERSION=$(xmlstarlet sel -N w="http://maven.apache.org/POM/4.0.0" -t -v '//w:project/w:version' -n pom.xml) - -(cd alfa-client && nx release version --projects alfa -d $NEWVERSION) - -echo -echo "NEXT STEPS:" -echo "***********" -echo "Änderungen prüfen" -echo "git commit -a -m 'release version "$NEWVERSION"'" -echo "git push" -echo "git tag "$NEWVERSION -echo "git push --tags" diff --git a/release-startdev.sh b/release-startdev.sh deleted file mode 100755 index 49290eeba6165ff19c1023c2592576a3bd0412b8..0000000000000000000000000000000000000000 --- a/release-startdev.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den -# Ministerpräsidenten des Landes Schleswig-Holstein -# Staatskanzlei -# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung -# -# Lizenziert unter der EUPL, Version 1.2 oder - sobald -# diese von der Europäischen Kommission genehmigt wurden - -# Folgeversionen der EUPL ("Lizenz"); -# Sie dürfen dieses Werk ausschließlich gemäß -# dieser Lizenz nutzen. -# Eine Kopie der Lizenz finden Sie hier: -# -# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 -# -# Sofern nicht durch anwendbare Rechtsvorschriften -# gefordert oder in schriftlicher Form vereinbart, wird -# die unter der Lizenz verbreitete Software "so wie sie -# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - -# ausdrücklich oder stillschweigend - verbreitet. -# Die sprachspezifischen Genehmigungen und Beschränkungen -# unter der Lizenz sind dem Lizenztext zu entnehmen. -# - - -#set -x - -if [ "$#" -ne 1 ]; then - echo "Aufruf: ozg-release-startdev.sh NEWVERSION" - exit 1 -fi - -NEWVERSION=$1 - -echo - -# pom.xml:main -> project.version setzen -# projectname/pom.xml:parent -> project.parent.version setzen -# projectname/pom.xml:parent,main -> project.parent.version und project.version setzen -# -PROJECTS="pom.xml:main - alfa-service/pom.xml:parent - alfa-server/pom.xml:parent - alfa-client/pom.xml:parent " - -for PROJECT in $PROJECTS; -do - POMFILE=$(echo $PROJECT | cut -d':' -f1) - ACTIONS=$(echo $PROJECT | cut -d':' -f2) - - ## Auf SNAPSHOT Versionen testen - if fgrep -q "SNAPSHOT" $POMFILE; then - RED='\033[0;31m' - NC='\033[0m' - echo "${RED}ERROR: Datei "$POMFILE" enthält noch SNAPSHOT Versionen, das sollte hier nicht passieren.${NC}" - exit 1 - fi - - ## Versionen setzen - if [[ $ACTIONS == *"main"* ]] ; then - xmlstarlet ed --pf -L -N w="http://maven.apache.org/POM/4.0.0" -u '//w:project/w:version' -v $NEWVERSION $POMFILE - fi - - if [[ $ACTIONS == *"parent"* ]]; then - xmlstarlet ed --pf -L -N w="http://maven.apache.org/POM/4.0.0" -u '//w:project/w:parent/w:version' -v $NEWVERSION $POMFILE - fi -done - - - -echo -echo "NEXT STEPS:" -echo "***********" -echo "Änderungen prüfen" -echo "git commit -a -m 'start development "$NEWVERSION"'" -echo "git push" -