-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add error formatting #178
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { IFormatters } from "./ngx-error.pipe"; | ||
|
||
function format(msg: string, displayName?: string) { | ||
return displayName ? `${displayName || ''} ${(msg || '').toLowerCase()}` : msg; | ||
} | ||
|
||
export const errorDefaultMessages: IFormatters = { | ||
max: (displayName?: string, data?: any) => format(`Cannot be more than ${data?.max}`, displayName), | ||
maxlength: (displayName?: string, data?: any) => | ||
format(`Must be less than ${data?.requiredLength} characters`, displayName), | ||
min: (displayName?: string, data?: any) => format(`Must be at least ${data?.min}`, displayName), | ||
minlength: (displayName?: string, data?: any) => | ||
format(`Must be at least ${data?.requiredLength} characters`, displayName), | ||
required: (displayName?: string) => (displayName ? `${displayName} is required` : 'Required'), | ||
pattern: (displayName?: string) => format('Contains invalid characters', displayName), | ||
unique: (displayName?: string) => format('Must be unique', displayName), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { FormatErrorPipe } from './ngx-error.pipe'; | ||
import { errorDefaultMessages } from './ngx-error-default-messages'; | ||
import uuid from 'uuid'; | ||
|
||
describe(FormatErrorPipe.name, () => { | ||
let pipe!: FormatErrorPipe; | ||
let errorObject: any; | ||
let definedErrorMessageKey: string | undefined; | ||
let displayName: string | undefined; | ||
const getActualValue = () => pipe.transform(errorObject, displayName); | ||
|
||
describe(FormatErrorPipe.prototype.transform.name, () => { | ||
beforeAll(() => { | ||
pipe = new FormatErrorPipe(errorDefaultMessages); | ||
}); | ||
describe('a successful transformation', () => { | ||
describe('GIVEN error object has a member matching a defined error message key', () => { | ||
beforeEach(() => { | ||
definedErrorMessageKey = 'required'; | ||
errorObject = { [definedErrorMessageKey]: uuid.v4() }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these may need cleaned up or fixed because i didn't run them locally |
||
}); | ||
|
||
describe('WHEN optional field name is falsey', () => { | ||
let expectedErrorMessageWithoutDisplayName: any; | ||
|
||
beforeEach(() => { | ||
displayName = undefined; | ||
expectedErrorMessageWithoutDisplayName = new RegExp(definedErrorMessageKey!, 'i'); | ||
}); | ||
|
||
it('returns the defined message for the defined error key', () => { | ||
expect(expectedErrorMessageWithoutDisplayName.test(getActualValue())).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('WHEN optional display name is truthy', () => { | ||
let expectedErrorMessageWithDisplayName: any; | ||
beforeEach(() => { | ||
displayName = uuid.v4(); | ||
expectedErrorMessageWithDisplayName = new RegExp(`${displayName}.*${definedErrorMessageKey}`, 'i'); | ||
}); | ||
it('returns the defined message with the display name', () => { | ||
expect(expectedErrorMessageWithDisplayName.test(getActualValue())).toBe(true); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('GIVEN edge cases', () => { | ||
describe('WHEN error object is falsey but error key is one of defined messages', () => { | ||
beforeEach(() => { | ||
errorObject = null; | ||
definedErrorMessageKey = 'required'; | ||
}); | ||
it('returns an empty string', () => { | ||
expect(getActualValue()).toBe(''); | ||
}); | ||
}); | ||
|
||
describe('WHEN error object is falsey and error key is falsey', () => { | ||
beforeEach(() => { | ||
errorObject = null; | ||
definedErrorMessageKey = undefined; | ||
}); | ||
it('returns an empty string', () => { | ||
expect(getActualValue()).toBe(''); | ||
}); | ||
}); | ||
|
||
describe('WHEN error object is truthy but has no members matching a defined key', () => { | ||
beforeEach(() => { | ||
errorObject = { [uuid.v4()]: true }; | ||
definedErrorMessageKey = 'required'; | ||
}); | ||
it('returns an empty string', () => { | ||
expect(getActualValue()).toBe(''); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Pipe, PipeTransform, Inject } from '@angular/core'; | ||
import { SUB_FORM_ERRORS_TOKEN } from './ngx-sub-form-tokens'; | ||
|
||
export interface IFormatter { | ||
(controlDisplayName?: string, data?: any): string; | ||
} | ||
|
||
export interface IFormatters { | ||
[errorKey: string]: IFormatter; | ||
} | ||
|
||
@Pipe({ name: 'formatError' }) | ||
export class FormatErrorPipe implements PipeTransform { | ||
|
||
constructor(@Inject(SUB_FORM_ERRORS_TOKEN) private readonly formattedErrors: IFormatters) { } | ||
|
||
transform(err: any, controlName?: string) { | ||
return this.getErrorMessage(this.formattedErrors, err, controlName); | ||
} | ||
|
||
private getErrorMessage(formattedErrors: any, controlErrors: any, formControlDisplayName?: string) { | ||
const errors = Object.keys(controlErrors || {}); | ||
|
||
if (errors.length) { | ||
const validatorName: string = errors[0]; | ||
const validationData: any = (controlErrors || {})[validatorName]; | ||
const messager: any = (formattedErrors as any)[validatorName]; | ||
|
||
return messager ? messager(formControlDisplayName, validationData).trim() : ''; | ||
} | ||
|
||
return ''; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,8 +60,8 @@ | |
/> | ||
</mat-form-field> | ||
|
||
<mat-error data-input-image-url-error *ngIf="formGroupErrors?.imageUrl?.required"> | ||
Image url is required | ||
<mat-error> | ||
{{formGroupErrors?.imageUrl | formatError: 'Image Path'}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the argument is optional. when provided, it is displayed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only one error at a time is shown based on the order of the FormControl's validators array |
||
</mat-error> | ||
|
||
<mat-form-field> | ||
|
@@ -75,8 +75,8 @@ | |
/> | ||
</mat-form-field> | ||
|
||
<mat-error data-input-price-error *ngIf="formGroupErrors?.price?.required"> | ||
Price is required | ||
<mat-error> | ||
{{formGroupErrors?.price | formatError}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. example with no argument |
||
</mat-error> | ||
|
||
<mat-form-field> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,7 @@ interface OneListingForm { | |
@Component({ | ||
selector: 'app-listing-form', | ||
templateUrl: './listing-form.component.html', | ||
styleUrls: ['./listing-form.component.scss'], | ||
styleUrls: ['./listing-form.component.scss'] | ||
}) | ||
// export class ListingFormComponent extends NgxAutomaticRootFormComponent<OneListing, OneListingForm> | ||
export class ListingFormComponent extends NgxRootFormComponent<OneListing, OneListingForm> { | ||
|
@@ -59,8 +59,8 @@ export class ListingFormComponent extends NgxRootFormComponent<OneListing, OneLi | |
listingType: new FormControl(null, Validators.required), | ||
id: new FormControl(null, Validators.required), | ||
title: new FormControl(null, Validators.required), | ||
imageUrl: new FormControl(null, Validators.required), | ||
price: new FormControl(null, Validators.required), | ||
imageUrl: new FormControl(null, {validators: [Validators.required, Validators.minLength(4), Validators.maxLength(8)]}), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these configurable Validators.* parameter values are displayed in the error messages by default |
||
price: new FormControl(null, {validators: [Validators.required, Validators.min(0), Validators.max(5)]}), | ||
}; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these can be overridden by passing an argument to the
subFormErrorProvider
helper in utils