diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts index 10218c11dcf905b85f05989b4a813eed123d97b4..8ba31e2037fb82d5335bcfb39c1c0656ff5239d2 100644 --- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts @@ -21,37 +21,31 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - AbstractControl, - FormControl, - FormGroup, - UntypedFormControl, - UntypedFormGroup, -} from '@angular/forms'; -import { createIssue } from '../../../test/error'; +import { AbstractControl, FormControl, FormGroup, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { createInvalidParam, createIssue } from '../../../test/error'; import { InvalidParam, Issue } from '../tech.model'; import { getControlForIssue, getMessageForInvalidParam, getMessageForIssue, + setInvalidParamValidationError, setIssueValidationError, } from './tech.validation.util'; -import { ValidationMessageCode } from './tech.validation.messages'; describe('ValidationUtils', () => { - describe('set issue validation error', () => { - const baseField1Control: FormControl = new UntypedFormControl(); - const baseField2Control: FormControl = new UntypedFormControl(); - const subGroupFieldControl: FormControl = new UntypedFormControl(); - - const form: FormGroup = new UntypedFormGroup({ - baseField1: baseField1Control, - baseField2: baseField2Control, - subGroup: new UntypedFormGroup({ - subGroupField1: subGroupFieldControl, - }), - }); + const baseField1Control: FormControl = new UntypedFormControl(); + const baseField2Control: FormControl = new UntypedFormControl(); + const subGroupFieldControl: FormControl = new UntypedFormControl(); + + const form: FormGroup = new UntypedFormGroup({ + baseField1: baseField1Control, + baseField2: baseField2Control, + subGroup: new UntypedFormGroup({ + subGroupField1: subGroupFieldControl, + }), + }); + describe('set issue validation error', () => { describe('get control for issue', () => { it('should return base field control', () => { const issue: Issue = { ...createIssue(), field: 'baseField1' }; @@ -149,81 +143,97 @@ describe('ValidationUtils', () => { }); }); - describe('get message for invalid-params-item', () => { - const item: InvalidParam = { - name: 'name-of-field', - reason: ValidationMessageCode.VALIDATION_FIELD_EMPTY, - }; - + describe('getMessageForInvalidParam', () => { it('should return message', () => { - const msg: string = getMessageForInvalidParam(item); + const invalidParam: InvalidParam = createInvalidParam(); - expect(msg).toEqual(`Bitte ${item.name} ausfüllen`); + const msg: string = getMessageForInvalidParam(invalidParam); + + expect(msg).toEqual(`Bitte ${invalidParam.name} ausfüllen`); }); }); -}); -// describe('setValidationDetails', () => { -// let form: UntypedFormGroup; -// const fieldName = 'test-field'; - -// beforeEach(() => { -// form = formService.form; -// const control = new FormControl(''); -// form.addControl(fieldName, control); -// }); - -// it('should set invalid-params of validation-problem-details on control', () => { -// const validationDetails: ProblemDetail = createAbsenderNameProblemDetail(); -// const item = validationDetails['invalid-params'][0]; -// item.name = '.' + fieldName; - -// formService.setErrorByProblemDetail(validationDetails); - -// const itemError = form.getError(item.reason, fieldName); -// expect(itemError).toBe( -// getMessageForInvalidParamsItem({ -// ...item, -// name: fieldName, -// }), -// ); -// }); -// }); - -// describe('set error by api error', () => { -// // let form: UntypedFormGroup; -// const fieldName: string = 'test-field'; - -// beforeEach(() => { -// // form = formService.form; -// // const control = ; -// formService.form.addControl(fieldName, new FormControl('')); -// }); - -// it('should set issues of api-error on control', () => { -// const issue: Issue = { ...createIssue(), field: TestFormService + '.' + fieldName }; -// const apiError: ApiError = createApiError([issue]); - -// formService.setErrorByApiError(apiError); - -// expect(formService.form.getError(issue.messageCode, fieldName)).toBe(issue); -// }); -// }); - -// export function createAbsenderNameProblemDetail(invalidParams: InvalidParam[]): ProblemDetail { -// return { -// type: 'about:blank', -// title: 'Unprocessable Entity', -// status: HttpStatusCode.UnprocessableEntity, -// detail: 'settingsBody.absender.name: validation_field_empty', -// instance: '/api/configuration/settings/65df039a69eeafef365a42dd', -// 'invalid-params': invalidParams, -// }; -// } - -// export function createAbsenderNameInvalidParam(): InvalidParam { -// return { -// name: 'settingsBody.absender.name', -// reason: ValidationMessageCode.VALIDATION_FIELD_EMPTY, -// }; -// } + describe('setInvalidParamValidationError', () => { + const formPrefixes = ['', 'some-prefix']; + const fieldNames = ['baseField1', 'baseField2', 'subGroup.subGroupField1']; + const prefixNameCombinations = formPrefixes.flatMap((prefix) => + fieldNames.map((name) => [prefix, name]), + ); + const unknownName = 'unknown-field'; + + describe.each(prefixNameCombinations)( + 'with prefix "%s" and fieldName "%s"', + (prefix, fieldName) => { + let invalidParam: InvalidParam; + let invalidParamWithoutPrefix: InvalidParam; + + beforeEach(() => { + form.reset(); + invalidParamWithoutPrefix = createInvalidParam(); + invalidParamWithoutPrefix.name = fieldName; + invalidParam = { + reason: invalidParamWithoutPrefix.reason, + name: prefix.length ? `${prefix}.${fieldName}` : fieldName, + }; + }); + + it('should assign invalidParam to form control error without prefix', () => { + const message = getMessageForInvalidParam(invalidParamWithoutPrefix); + + setInvalidParamValidationError(form, invalidParam, prefix); + + const errorMessage = form.getError(invalidParam.reason, fieldName); + expect(errorMessage).toBe(message); + }); + + it('should mark form as touched', () => { + setInvalidParamValidationError(form, invalidParam, prefix); + + expect(form.touched).toBeTruthy(); + }); + }, + ); + + describe.each([ + ['', '', 'unknown-field'], + ['valid-prefix', 'valid-prefix', 'unknown-field'], + ['valid-prefix', 'valid-prefix', 'unknown-field'], + ['unknown-prefix', 'valid-prefix', 'unknown-field'], + ['unknown-prefix', 'valid-prefix', 'baseField1'], + ])( + 'with pathPrefix "%s", paramPrefix "%s", and field-name "%s"', + (pathPrefix, paramPrefix, fieldName) => { + let invalidParam: InvalidParam; + + beforeEach(() => { + form.reset(); + invalidParam = createInvalidParam(); + invalidParam.name = paramPrefix.length > 0 ? `${paramPrefix}.${fieldName}` : fieldName; + }); + + it('should not assign to field control error', () => { + setInvalidParamValidationError(form, invalidParam, pathPrefix); + + const errorMessage = form.getError(invalidParam.reason, unknownName); + expect(errorMessage).toBeFalsy(); + }); + + it('should not mark as touched', () => { + setInvalidParamValidationError(form, invalidParam, pathPrefix); + + expect(form.touched).toBeFalsy(); + }); + }, + ); + + it('should not assign invalidParam with incorrect prefix', () => { + const invalidParam: InvalidParam = createInvalidParam(); + invalidParam.name = `correct-prefix.baseField1`; + + setInvalidParamValidationError(form, invalidParam, 'incorrect-prefix'); + + const errorMessage = form.getError(invalidParam.reason, unknownName); + expect(errorMessage).toBeFalsy(); + }); + }); +}); diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts index ff661a65b78a6e4d668c5b97afe1d83ab98ebf1d..b61f1b1059b1324d1f60d357b68f5e767f859427 100644 --- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts +++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts @@ -21,10 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { AbstractControl, FormGroup, UntypedFormGroup } from '@angular/forms'; +import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { isNil } from 'lodash-es'; import { ApiError, InvalidParam, Issue, IssueParam } from '../tech.model'; -import { replacePlaceholder } from '../tech.util'; +import { isNotNil, replacePlaceholder } from '../tech.util'; import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages'; export function isValidationError(issue: Issue): boolean { @@ -80,47 +80,25 @@ export function getMessageCode(apiError: ApiError): string { return apiError.issues[0].messageCode; } -//TODO TDD entwickeln - kleine Tests export function setInvalidParamValidationError( form: UntypedFormGroup, invalidParam: InvalidParam, - pathPrefix?: string, + pathPrefix: string = '', ): void { - const item: InvalidParam = itemWithoutNamePrefix(invalidParam, pathPrefix); + const item: InvalidParam = mapInvalidParamWithoutPrefix(invalidParam, pathPrefix); const formControl: AbstractControl = form.get(item.name); - - formControl.setErrors({ [item.reason]: getMessageForInvalidParam(item) }); - formControl.markAsTouched(); + if (isNotNil(formControl)) { + formControl.setErrors({ [item.reason]: getMessageForInvalidParam(item) }); + formControl.markAsTouched(); + } } -//Das Verb fehlt bei der Function -function itemWithoutNamePrefix(item: InvalidParam, pathPrefix?: string): InvalidParam { - const namePath: string = item.name; - //Was wird hier genau gemacht? warum? - const pathPrefixIrgendwas: string = pathPrefix + (pathPrefix.endsWith('.') ? '' : '.'); - //Der pathPrefix wird doch einmal konfiguriert und dann sollte das doch passen, wann und warum genau soll hier der Error geworfen werden? - if (!namePath.startsWith(pathPrefix)) - throw Error(`Expected prefix ${pathPrefix} not found: ${namePath}`); - //Ich finde das sehr merkwürdig, dass hier was im Item manipuliert und dann zurückgegeben wird, wieso nicht einfach den name zurückgeben? - return { ...item, name: namePath.substring(pathPrefixIrgendwas.length) }; +function mapInvalidParamWithoutPrefix(item: InvalidParam, pathPrefix: string): InvalidParam { + return pathPrefix.length > 0 ? + { ...item, name: item.name.substring(pathPrefix.length + 1) } + : item; } export function getMessageForInvalidParam(item: InvalidParam): string { - let formatString: string = VALIDATION_MESSAGES[item.reason] ?? item.reason; - - //Wieso wird hier durchiteriert? Das ist doch ein Item mit name und reason oder nicht? - for (const [itemField, value] of Object.entries(item)) { - //Warum genau wird "das" hier aufgebaut? - const itemFieldToPlaceholder = { - name: 'field', - }; - formatString = replacePlaceholder( - formatString, - //Verstehe ich auch nicht, wieso nicht einfach 'field'? - itemFieldToPlaceholder[itemField] ?? itemField, - value, - ); - } - return formatString; + return replacePlaceholder(VALIDATION_MESSAGES[item.reason], 'field', item.name); } -//