Skip to content

Commit

Permalink
Merge branch 'main' into checkbox-group-formgroup-type-adjust
Browse files Browse the repository at this point in the history
  • Loading branch information
videoeero committed Dec 4, 2024
2 parents 4898f7d + b764798 commit 1994b3d
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { Component, ViewChild } from '@angular/core';
import { MockComponents } from 'ng-mocks';
import { BehaviorSubject } from 'rxjs';
import { IconComponent } from '../../../icon/icon.component';
import { Component, EventEmitter, Output, ViewChild } from '@angular/core';
import { FudisValidationErrors, FudisValidators } from '../../../../utilities/form/validators';
import { ErrorMessageComponent } from './error-message.component';
import { ErrorMessageDirective } from './error-message.directive';
import { TextInputComponent } from '../../text-input/text-input.component';
import { LabelComponent } from '../../label/label.component';
import { GuidanceComponent } from '../../guidance/guidance.component';
import { ValidatorErrorMessageComponent } from '../validator-error-message/validator-error-message.component';
import { FudisInternalErrorSummaryService } from '../../../../services/form/error-summary/internal-error-summary.service';

const observableMessage = new BehaviorSubject<string>('Test error message');

const controlErrors = {
required: {
message: 'This is required',
},
'fudis-error-message-1': {
message: observableMessage,
},
required: 'This is required',
'fudis-error-message-1': 'Test error message',
};

const controlOnlyRequiredError = {
Expand All @@ -45,17 +36,26 @@ const errorToRemove: FudisValidationErrors = {
selector: 'fudis-mock-test-error',
template: `
<fudis-text-input [control]="control" [label]="'Test label'">
<fudis-error-message #testError *ngIf="errorVisible" [message]="message" />
<fudis-error-message
#testError
*ngIf="errorVisible"
(handleAddError)="handleAddError.emit($event)"
(handleRemoveError)="handleRemoveError.emit($event)"
[message]="message"
/>
</fudis-text-input>
`,
})
class MockTestErrorComponent {
@ViewChild('testError') testError: ErrorMessageComponent;
@ViewChild('testError') testError: ErrorMessageDirective;

message = 'Test error message';

errorVisible: boolean;

@Output() handleAddError = new EventEmitter<FudisValidationErrors>();
@Output() handleRemoveError = new EventEmitter<FudisValidationErrors>();

control: FormControl = new FormControl('', FudisValidators.required('This is required'));
}

Expand All @@ -66,12 +66,12 @@ describe('ErrorMessageComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
ErrorMessageComponent,
IconComponent,
ErrorMessageDirective,
MockTestErrorComponent,
TextInputComponent,
ValidatorErrorMessageComponent,
MockComponents(LabelComponent, GuidanceComponent),
LabelComponent,
GuidanceComponent,
],
imports: [ReactiveFormsModule],
providers: [FudisInternalErrorSummaryService],
Expand All @@ -91,16 +91,11 @@ describe('ErrorMessageComponent', () => {
fixture.detectChanges();
});

it('should emit addError', () => {
jest.spyOn(component.testError.handleAddError, 'emit');
component.testError.ngOnInit();
fixture.detectChanges();

expect(component.testError.handleAddError.emit).toHaveBeenCalledWith(errorReceived);
});

it('should have proper errors in control when component is created', () => {
expect(component.control.errors).toEqual(controlErrors);
expect(component.control.errors?.['required'].message).toEqual(controlErrors.required);
expect(component.control.errors?.['fudis-error-message-1'].message.value).toEqual(
controlErrors['fudis-error-message-1'],
);
});

it('should have only required error in control when component is destroyed', () => {
Expand All @@ -110,14 +105,6 @@ describe('ErrorMessageComponent', () => {
expect(component.control.errors).toEqual(controlOnlyRequiredError);
});

it('should emit removeError message when component is destroyed', () => {
jest.spyOn(component.testError.handleRemoveError, 'emit');
component.testError.ngOnDestroy();
fixture.detectChanges();

expect(component.testError.handleRemoveError.emit).toHaveBeenCalledWith(errorToRemove);
});

it('should have first invalid and then valid control when all errors are removed', () => {
expect(component.control.invalid).toBe(true);
component.control.patchValue('Add value to remove required error');
Expand All @@ -129,14 +116,37 @@ describe('ErrorMessageComponent', () => {
expect(component.control.valid).toBe(true);
});

it('should update string message', () => {
it('should update string message', async () => {
const newError = 'Error message updated';

observableMessage.next(newError);
component.message = newError;

fixture.detectChanges();

expect(component.control?.errors?.['fudis-error-message-1'].message.value).toEqual(newError);
});
});

describe('emitters', () => {
it('should emit addError when directive is created', () => {
jest.spyOn(component.handleAddError, 'emit');
component.errorVisible = true;
fixture.detectChanges();

expect(component.handleAddError.emit).toHaveBeenCalledWith(errorReceived);
});

it('should emit removeError message when directive is destroyed', () => {
jest.spyOn(component.handleRemoveError, 'emit');
component.errorVisible = true;
fixture.detectChanges();

expect(component.handleRemoveError.emit).not.toHaveBeenCalled();

component.errorVisible = false;
fixture.detectChanges();

expect(component.control.errors).toEqual(controlErrors);
expect(component.handleRemoveError.emit).toHaveBeenCalledWith(errorToRemove);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
ChangeDetectionStrategy,
Component,
DestroyRef,
Directive,
EventEmitter,
Host,
inject,
Expand Down Expand Up @@ -34,12 +33,10 @@ import { FudisComponentChanges } from '../../../../types/miscellaneous';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FudisCheckboxGroupFormGroup } from '../../../../types/forms';

@Component({
@Directive({
selector: 'fudis-error-message',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ErrorMessageComponent implements OnInit, OnChanges, OnDestroy {
export class ErrorMessageDirective implements OnInit, OnChanges, OnDestroy {
constructor(
private _errorSummaryService: FudisInternalErrorSummaryService,
private _translationService: FudisTranslationService,
Expand Down Expand Up @@ -137,7 +134,7 @@ export class ErrorMessageComponent implements OnInit, OnChanges, OnDestroy {
}
}

ngOnChanges(changes: FudisComponentChanges<ErrorMessageComponent>): void {
ngOnChanges(changes: FudisComponentChanges<ErrorMessageDirective>): void {
const newMessage = changes.message?.currentValue;

if (newMessage !== changes.message?.previousValue) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { StoryFn, Meta, moduleMetadata } from '@storybook/angular';
import { action } from '@storybook/addon-actions';
import { FormControl, ReactiveFormsModule, FormsModule, FormControlOptions } from '@angular/forms';
import { Component } from '@angular/core';
import { Component, EventEmitter, Output } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { ErrorMessageComponent } from './error-message.component';
import { ErrorMessageDirective } from './error-message.directive';
import readme from './readme.mdx';
import { FudisValidators } from '../../../../utilities/form/validators';
import { FudisValidationErrors, FudisValidators } from '../../../../utilities/form/validators';
import { excludeAllRegex, errorMessageExclude } from '../../../../utilities/storybook';

@Component({
Expand All @@ -17,8 +18,18 @@ import { excludeAllRegex, errorMessageExclude } from '../../../../utilities/stor
[control]="control"
[label]="'Focus to input'"
>
<fudis-error-message *ngIf="_errorExists" [message]="observableMessage" />
<fudis-error-message *ngIf="_errorExists" [message]="stringMessage" />
<fudis-error-message
*ngIf="_errorExists"
(handleAddError)="handleAddError.emit($event)"
(handleRemoveError)="handleRemoveError.emit($event)"
[message]="observableMessage"
/>
<fudis-error-message
*ngIf="_errorExists"
(handleAddError)="handleAddError.emit($event)"
(handleRemoveError)="handleRemoveError.emit($event)"
[message]="stringMessage"
/>
</fudis-text-input>
<fudis-button
(click)="toggleCustomError()"
Expand Down Expand Up @@ -56,6 +67,9 @@ class TextInputWithErrorMessageComponent {

protected _errorExists: boolean = false;

@Output() handleAddError = new EventEmitter<FudisValidationErrors>();
@Output() handleRemoveError = new EventEmitter<FudisValidationErrors>();

toggleCustomError(): void {
this._errorExists = !this._errorExists;
}
Expand All @@ -79,7 +93,7 @@ class TextInputWithErrorMessageComponent {

export default {
title: 'Components/Form/Error Message',
component: ErrorMessageComponent,
component: ErrorMessageDirective,
decorators: [
moduleMetadata({
declarations: [TextInputWithErrorMessageComponent],
Expand Down Expand Up @@ -110,6 +124,7 @@ const Template: StoryFn = (args) => ({
'',
FudisValidators.required('This validation message is send by Fudis Validators'),
),
addError: action('addError'),
},
template: html`
<fudis-body-text
Expand All @@ -121,7 +136,7 @@ const Template: StoryFn = (args) => ({
message.</fudis-body-text
>
<fudis-text-input [control]="control" [label]="'Focus to input'">
<fudis-error-message [message]="message" />
<fudis-error-message (handleAddError)="addError($event)" [message]="message" />
</fudis-text-input>
`,
});
Expand All @@ -133,8 +148,14 @@ Example.args = {

export const ExampleWithObservableError: StoryFn = (args) => ({
...args,
props: {
handleAddError: action('handleAddError'),
handleRemoveError: action('handleRemoveError'),
},
template: `
<example-text-input-with-error-message></example-text-input-with-error-message>
<example-text-input-with-error-message
(handleAddError)="handleAddError($event)"
(handleRemoveError)="handleRemoveError($event)"></example-text-input-with-error-message>
`,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { ArgTypes, Meta, Canvas } from "@storybook/blocks";
import * as ErrorMessageStories from "./error-message.stories";
import { ErrorMessageComponent } from "./error-message.component";
import { ErrorMessageDirective } from "./error-message.directive";

<Meta title="Components/Form/Error Message" />

# Error Message

Form component's errors can be achieved in two ways:
Form field components' errors can be achieved in two ways:

- Through form control's validators: [Fudis Validators](/docs/utilities-validators--documentation).
- As an Error Message child component, for cases when using a validator or creating a custom one can be tricky.
- Through Form Control's validators: [Fudis Validators](/docs/utilities-validators--documentation).
- As an Error Message Directive, for cases when using a validator or creating a custom one can be tricky.

If your error message repeats multiple times across your form or application, it is recommended to create your own [Fudis Validator](/docs/utilities-validators--documentation).

This page is documentation for the latter Error Message Component, which binds custom error message to Fudis Form components through content projection.
This page is documentation for the latter Error Message Directive, which binds custom error message to form field components through content projection.

This requires the use of selector `fudis-error-message`. Both Error Message and Fudis Validator errors create the same UI result and renders errors under the Guidance Component.

Expand Down Expand Up @@ -42,7 +42,7 @@ Message parameter can be passed either as a string or as an observable string.

The following example demostrates how custom error message can be used along with Fudis Validator error messages. By toggling 'Switch Message Content' button will dynamically update error message content.

<Canvas of={ErrorMessageStories.Example} />
<Canvas of={ErrorMessageStories.ExampleWithObservableError} />

## Accessibility

Expand All @@ -63,8 +63,4 @@ The following example demostrates how custom error message can be used along wit

- [Error Summary](/docs/components-form-error-summary--documentation)

<ArgTypes of={ErrorMessageComponent} />

```
```
<ArgTypes of={ErrorMessageDirective} />
6 changes: 3 additions & 3 deletions ngx-fudis/projects/ngx-fudis/src/lib/ngx-fudis.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { DialogComponent } from './components/dialog/dialog.component';
import { DropdownMenuComponent } from './components/dropdown-menu/dropdown-menu.component';
import { DropdownMenuItemComponent } from './components/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component';
import { DropdownMenuGroupComponent } from './components/dropdown-menu/dropdown-menu-group/dropdown-menu-group.component';
import { ErrorMessageComponent } from './components/form/error-message/error-message/error-message.component';
import { ErrorSummaryComponent } from './components/form/error-summary/error-summary.component';
import { ExpandableComponent } from './components/expandable/expandable.component';
import { FieldSetComponent } from './components/form/fieldset/fieldset.component';
Expand Down Expand Up @@ -92,6 +91,7 @@ import {
} from './directives/dialog/dialog-directives';
import { DropdownBaseDirective } from './directives/form/dropdown-base/dropdown-base.directive';
import { DropdownItemBaseDirective } from './directives/form/dropdown-item-base/dropdown-item-base.directive';
import { ErrorMessageDirective } from './components/form/error-message/error-message/error-message.directive';
import { FormCommonApiDirective } from './directives/form/form-common-api/form-common-api.directive';
import { FormSubmitDirective } from './directives/form/form-actions/form-actions.directive';
import { GridApiDirective } from './directives/grid/grid-api/grid-api.directive';
Expand Down Expand Up @@ -156,7 +156,7 @@ import { FudisTranslationService } from './services/translation/translation.serv
DropdownMenuComponent,
DropdownItemBaseDirective,
DropdownMenuItemComponent,
ErrorMessageComponent,
ErrorMessageDirective,
ErrorSummaryComponent,
ExpandableComponent,
FieldSetComponent,
Expand Down Expand Up @@ -257,7 +257,7 @@ import { FudisTranslationService } from './services/translation/translation.serv
DropdownMenuGroupComponent,
DropdownMenuComponent,
DropdownMenuItemComponent,
ErrorMessageComponent,
ErrorMessageDirective,
ExpandableComponent,
FooterComponent,
FooterContentLeftDirective,
Expand Down
2 changes: 1 addition & 1 deletion ngx-fudis/projects/ngx-fudis/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export { FudisValidators } from './lib/utilities/form/validators';
export { DropdownMenuGroupComponent } from './lib/components/dropdown-menu/dropdown-menu-group/dropdown-menu-group.component';
export { DropdownMenuComponent } from './lib/components/dropdown-menu/dropdown-menu.component';
export { DropdownMenuItemComponent } from './lib/components/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component';
export { ErrorMessageComponent } from './lib/components/form/error-message/error-message/error-message.component';
export { ErrorMessageDirective } from './lib/components/form/error-message/error-message/error-message.directive';
export { FudisErrorSummaryService } from './lib/services/form/error-summary/error-summary.service';
export { ExpandableComponent } from './lib/components/expandable/expandable.component';
export { FieldSetComponent } from './lib/components/form/fieldset/fieldset.component';
Expand Down

0 comments on commit 1994b3d

Please sign in to comment.