Skip to content

Commit

Permalink
fix: working and adjusted concardis-directdebit validation (#590)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxKless authored Mar 2, 2021
1 parent a695bc4 commit deaed69
Show file tree
Hide file tree
Showing 19 changed files with 121 additions and 92 deletions.
79 changes: 8 additions & 71 deletions docs/guides/formly.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,72 +192,9 @@ It defines a FormlyModule with preconfigured sample field types.

### Testing Custom Types

<<<<<<< HEAD
To test a custom type, create a `FormlyTestingContainerComponent`, configure `FormlyModule` for testing and set an appropriate testing configuration.
A simple test for an example input component looks like this:

```typescript
describe('Example Input Field Component', () => {
let component: FormlyTestingContainerComponent;
let fixture: ComponentFixture<FormlyTestingContainerComponent>;
let element: HTMLElement;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ExampleInputFieldComponent],
imports: [
FormlyModule.forRoot({
types: [
{
name: 'example-input-field',
component: ExampleInputFieldComponent,
},
],
}),
FormlyTestingComponentsModule,
],
}).compileComponents();
});

beforeEach(() => {
const testComponentInputs = {
fields: [
{
key: 'input',
type: 'example-input-field',
templateOptions: {
label: 'test label',
required: true,
},
} as FormlyFieldConfig,
],
form: new FormGroup({}),
model: {
input: '',
},
};
fixture = TestBed.createComponent(FormlyTestingContainerComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;

component.testComponentInputs = testComponentInputs;
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
expect(element.querySelector('example-input-field')).toBeTruthy();
});
});
```

=======
To test a custom type, you need to create a `FormlyTestingContainerComponent`, configure `FormlyModule` for testing and set an appropriate testing configuration.
You can find a simple example of a custom type test in [text-input.field.component.spec.ts](../../src/app/shared/formly/types/text-input-field/text-input-field.component.spec.ts).

> > > > > > > 241c4cbd3 (formly.md changes)
### Testing Wrappers

To test custom wrappers, create a `FormlyTestingContainerComponent` component, configure the `FormlyModule` with an example type (for example `FormlyTestingExampleComponent`) and the wrapper and set an appropriate testing configuration.
Expand Down Expand Up @@ -296,14 +233,14 @@ Refer to the tables below for an overview of these parts.

### Wrappers

| Name | Functionality | Relevant templateOptions |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| form-field-horizontal | Adds a label next to the field and adds red styling for invalid fields. | `labelClass`& `fieldClass`: Classes that will be added to the label or field `<div>` |
| form-field-checkbox-horizontal | Adds a label for a checkbox field, adds red styling and error messages for invalid fields. Uses `validators.validation` and `validation.messages` properties. | `labelClass`& `fieldClass`: Classes that will be added to the label or the outer field `<div>` |
| validation | Adds validation icons and error messages to the field. Uses `validators.validation` and `validation.messages` properties. | ---- |
| textarea-description | Adds a description to textarea fields, including the amount of remaining characters. | `maxLength`: Specifies the maximum length to be displayed in the message. |
| decription | Adds a custom description to any field | `customDescription`: `string` or `{key: string; args: any}` that will be translated |
| tooltip | Adds a tooltip to a field. Includes `<ish-field-tooltip>` component. | `tooltip`: `{ title?: string; text: string; link: string }` that defines the different tooltip texts. |
| Name | Functionality | Relevant templateOptions |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| form-field-horizontal | Adds a label next to the field and adds red styling for invalid fields. | `labelClass`& `fieldClass`: Classes that will be added to the label or field `<div>` |
| form-field-checkbox-horizontal | Adds a label for a checkbox field, adds red styling and error messages for invalid fields. Uses `validators.validation` and `validation.messages` properties. | `labelClass`& `fieldClass`: Classes that will be added to the label or the outer field `<div>` |
| validation | Adds validation icons and error messages to the field. Uses `validators.validation` and `validation.messages` properties. | `showValidation`: `(field: FormlyFieldConfig) => boolean`: optional, used to determine whether to show validation checkmarks |
| textarea-description | Adds a description to textarea fields, including the amount of remaining characters. | `maxLength`: Specifies the maximum length to be displayed in the message. |
| decription | Adds a custom description to any field | `customDescription`: `string` or `{key: string; args: any}` that will be translated |
| tooltip | Adds a tooltip to a field. Includes `<ish-field-tooltip>` component. | `tooltip`: `{ title?: string; text: string; link: string }` that defines the different tooltip texts. |

### Extensions

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { NgModule } from '@angular/core';
import { FormlyModule } from '@ngx-formly/core';

import { SharedModule } from 'ish-shared/shared.module';

import { CheckoutPaymentPageComponent } from './checkout-payment-page.component';
import { CheckoutPaymentComponent } from './checkout-payment/checkout-payment.component';
import { serverValidationExtension } from './formly/server-validation.extension';
import { PaymentConcardisCreditcardCvcDetailComponent } from './payment-concardis-creditcard-cvc-detail/payment-concardis-creditcard-cvc-detail.component';
import { PaymentConcardisCreditcardComponent } from './payment-concardis-creditcard/payment-concardis-creditcard.component';
import { PaymentConcardisDirectdebitComponent } from './payment-concardis-directdebit/payment-concardis-directdebit.component';
Expand All @@ -12,7 +14,12 @@ import { PaymentCybersourceCreditcardComponent } from './payment-cybersource-cre
import { PaymentParameterFormComponent } from './payment-parameter-form/payment-parameter-form.component';

@NgModule({
imports: [SharedModule],
imports: [
FormlyModule.forChild({
extensions: [{ name: 'server-validation', extension: serverValidationExtension }],
}),
SharedModule,
],
declarations: [
CheckoutPaymentComponent,
CheckoutPaymentPageComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FormControl } from '@angular/forms';
import { FormlyExtension, FormlyFieldConfig } from '@ngx-formly/core';

/**
* Extension to enable server-side validation.
* It accesses the formstate with two properties: errors and changedSinceErrors.
* If these are set for a property, it will have an error.
* To use this extension, set the formState in the relevant component.
*/
export const serverValidationExtension: FormlyExtension = {
prePopulate: field => {
if (field.hide) {
return;
}
field.templateOptions = {
...field.templateOptions,
showValidation: (fld: FormlyFieldConfig) =>
field.formControl?.valid &&
field.formControl?.dirty &&
!field.options.formState.changedSinceErrors?.[fld.key as string],
};

field.validators = {
...field.validators,
serverError: (_: FormControl, fld: FormlyFieldConfig) =>
!(
fld.options.formState.errors?.[fld.key as string] &&
fld.options.formState.changedSinceErrors?.[fld.key as string] === false
),
};

field.validation = {
...field.validation,
messages: {
...field.validation?.messages,
serverError: '',
},
};

field.expressionProperties = {
...field.expressionProperties,
'validation.messages.serverError': (_, formState) => formState.errors?.[field.key as string],
};
},
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<formly-form [form]="parameterForm" [options]="options" [model]="model" [fields]="getFieldConfig()">
<formly-form [form]="parameterForm" [options]="options" [model]="model" [fields]="fieldConfig">
<div class="offset-md-4 col-md-8">
<ish-checkbox
*ngIf="paymentMethod && paymentMethod.saveAllowed"
Expand All @@ -8,7 +8,12 @@
data-testing-id="save-for-later-input"
></ish-checkbox>
<div class="form-group">
<button type="button" class="btn btn-primary" (click)="submitNewPaymentInstrument()">
<button
type="button"
class="btn btn-primary"
[disabled]="formSubmitted && parameterForm.invalid"
(click)="submitNewPaymentInstrument()"
>
{{ 'checkout.account.submit.button.label' | translate }}
</button>
<button type="button" class="btn btn-secondary" (click)="cancelNewPaymentInstrument()">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { Validators } from '@angular/forms';
import { FormlyFieldConfig } from '@ngx-formly/core';
import { takeUntil } from 'rxjs/operators';
import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core';
import { pairwise, startWith, takeUntil } from 'rxjs/operators';

import { ScriptLoaderService } from 'ish-core/utils/script-loader/script-loader.service';
import { markAsDirtyRecursive } from 'ish-shared/forms/utils/form-utils';
Expand Down Expand Up @@ -29,18 +29,48 @@ declare var PayEngine: any;
changeDetection: ChangeDetectionStrategy.Default,
})
// tslint:disable-next-line: rxjs-prefer-angular-takeuntil
export class PaymentConcardisDirectdebitComponent extends PaymentConcardisComponent {
export class PaymentConcardisDirectdebitComponent extends PaymentConcardisComponent implements OnInit {
constructor(protected scriptLoader: ScriptLoaderService, protected cd: ChangeDetectorRef) {
super(scriptLoader, cd);
}

options: FormlyFormOptions;

handleErrors(controlName: string, message: string) {
if (this.parameterForm.controls[controlName]) {
this.parameterForm.controls[controlName].markAsDirty();
this.parameterForm.controls[controlName].setErrors({ customError: message });
this.options.formState = {
...this.options.formState,
errors: {
...this.options.formState.errors,
[controlName]: message,
},
changedSinceErrors: {
...this.options.formState.changedSinceErrors,
[controlName]: false,
},
};
this.parameterForm.controls[controlName].updateValueAndValidity();
}
}

ngOnInit() {
super.formInit();
this.fieldConfig = this.getFieldConfig();
this.parameterForm.valueChanges
.pipe(startWith({}), pairwise(), takeUntil(this.destroy$))
.subscribe(([prevValues, currentValues]) =>
Object.keys(currentValues).forEach(key => {
if (
currentValues[key] !== prevValues[key] &&
this.options.formState.changedSinceErrors?.hasOwnProperty(key)
) {
this.options.formState.changedSinceErrors[key] = true;
this.parameterForm.get(key).updateValueAndValidity();
}
})
);
}

/* ---------------------------------------- load concardis script if component is visible ------------------------------------------- */

loadScript() {
Expand Down Expand Up @@ -207,14 +237,10 @@ export class PaymentConcardisDirectdebitComponent extends PaymentConcardisCompon
iban: string;
mandate: { mandateId: string; mandateText: string; directDebitType: string };
} = {
accountHolder: parameters.find(p => p.name === 'accountHolder')
? parameters.find(p => p.name === 'accountHolder').value
: undefined,
iban: parameters.find(p => p.name === 'IBAN') ? parameters.find(p => p.name === 'IBAN').value : undefined,
accountHolder: parameters.find(p => p.name === 'accountHolder')?.value,
iban: parameters.find(p => p.name === 'IBAN')?.value,
mandate: {
mandateId: parameters.find(p => p.name === 'mandateReference')
? parameters.find(p => p.name === 'mandateReference').value
: undefined,
mandateId: parameters.find(p => p.name === 'mandateReference')?.value,
mandateText: this.getParamValue('mandateText', ''),
directDebitType,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Output,
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { FormlyFormOptions } from '@ngx-formly/core';
import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core';
import { Subject } from 'rxjs';

import { Attribute } from 'ish-core/models/attribute/attribute.model';
Expand Down Expand Up @@ -50,6 +50,7 @@ export class PaymentConcardisComponent implements OnInit, OnChanges, OnDestroy {
/**
* form for parameters which don't come form payment host
*/
fieldConfig: FormlyFieldConfig[];
parameterForm: FormGroup;
model = {};
options: FormlyFormOptions = {};
Expand Down
6 changes: 3 additions & 3 deletions src/app/pages/registration/registration-page.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { FormlyModule } from '@ngx-formly/core';

import { SharedModule } from 'ish-shared/shared.module';

import { RegistrationAddressFieldComponent } from './registration-address-field/registration-address-field.component';
import { RegistrationHeadingFieldComponent } from './registration-heading-field/registration-heading-field.component';
import { RegistrationAddressFieldComponent } from './formly/registration-address-field/registration-address-field.component';
import { RegistrationHeadingFieldComponent } from './formly/registration-heading-field/registration-heading-field.component';
import { RegistrationTacFieldComponent } from './formly/registration-tac-field/registration-tac-field.component';
import { RegistrationPageComponent } from './registration-page.component';
import { RegistrationTacFieldComponent } from './registration-tac-field/registration-tac-field.component';

const registrationPageRoutes: Routes = [{ path: '', component: RegistrationPageComponent }];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
></fa-icon>
<ng-template #noError>
<fa-icon
*ngIf="field.formControl?.valid && (field.formControl?.dirty || field.options?.parentForm.submitted)"
*ngIf="
field?.templateOptions?.showValidation ? field.templateOptions.showValidation(field) : defaultShowValidation()
"
class="formly-validation-icon"
[icon]="['fas', 'check']"
data-testing-id="check-icon"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ import { FormlyFieldConfig } from '@ngx-formly/core';
export class ValidationIconsComponent {
@Input() field: FormlyFieldConfig;
@Input() showError: boolean;

defaultShowValidation() {
return this.field.formControl?.valid && (this.field.formControl?.dirty || this.field.options?.parentForm.submitted);
}
}
2 changes: 2 additions & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,9 @@
"^.*/src/app/shared/[a-z][a-z0-9-]+/components/([a-z][a-z0-9-]+)/\\1\\.component\\.ts$",
"^.*/projects/[a-z][a-z0-9-]+/src/app/components/([a-z][a-z0-9-]+)/\\1\\.component\\.ts$",
"^.*/src/app/shared/address-forms/components/([a-z][a-z0-9-]+)/\\1\\.factory\\.ts$",
// formly
"^.*/src/app/shared/formly/(components|wrappers|types|extensions|utils|dev)/.*$",
"^.*/src/app/pages/([a-z][a-z0-9-]+)/formly/.*$",
// identity providers
"^.*/src/app/core/identity-provider/.*$",
// aggregation modules
Expand Down

0 comments on commit deaed69

Please sign in to comment.