Skip to content

Commit

Permalink
PER-9446-form input
Browse files Browse the repository at this point in the history
Implementation of the form input component from the new component library.

I have kept the old implementation and added a way to also use it without a form control, also using a debounce + style changes
  • Loading branch information
crisnicandrei committed Feb 9, 2024
1 parent e975abe commit 9c92e7c
Show file tree
Hide file tree
Showing 39 changed files with 634 additions and 18 deletions.
21 changes: 21 additions & 0 deletions src/app/component-library/components.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* @format */
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
FaIconLibrary,
FontAwesomeModule,
} from '@fortawesome/angular-fontawesome';
import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormInputComponent } from './components/form-input/form-input.component';

@NgModule({
declarations: [FormInputComponent],
imports: [CommonModule, FontAwesomeModule, FormsModule, ReactiveFormsModule],
exports: [FormInputComponent],
})
export class ComponentsModule {
constructor(private library: FaIconLibrary) {
library.addIcons(faExclamationCircle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!-- @format -->
<div
class="input-vertical-label"
[ngClass]="[
'input-vertical-label-' + variant,
isLabelHidden() ? 'hidden' : ''
]"
>
{{ placeholder }}
</div>
<fa-icon
*ngIf="errors"
class="error-icon"
[icon]="['fas', 'exclamation-circle']"
></fa-icon>
<input
[type]="type"
(input)="onInputChange($event.target.value)"
class="form-control form-input"
[ngClass]="[
'form-input-' + variant,
config && config.validateDirty ? 'validate-dirty' : '',
errors ? 'form-input-errors' : ''
]"
[id]="fieldName"
[name]="fieldName"
[placeholder]="placeholder"
[formControl]="control"
/>
<div class="input-vertical-error" [ngClass]="{ hidden: !errors }">
{{ errors }}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* @format */
@import 'fonts';
@import 'variables';

:host {
position: relative;
}

.form-input {
border-radius: 12px;
font-family: 'UsualRegular', sans-serif;
font-size: 14px;
line-height: 24px;
font-weight: 600;
border: 1px solid $form-border;
padding: 12px;
height: 48px;

&-errors {
background-color: $form-background-error;
border: 1px solid $form-border-error;
}

&:focus {
box-shadow: none;
background-color: $form-background-focus;
border: 1px solid $form-border-focus;
}

&::placeholder {
text-transform: uppercase;
font-family: 'UsualRegular', sans-serif;
line-height: 16px;
letter-spacing: 16%;
font-weight: 400;
font-size: 10px;
}

&-default {
color: $form-default-text-color;
}

&-dark {
background-color: $form-background-dark;
opacity: 0.8;
color: white;
border: 1px solid transparent;
&:focus {
background-color: $form-background-dark !important;
opacity: 1;
border: 1px solid $form-border;
}
&::placeholder {
color: white;
}
}
}

.input-vertical-label {
position: absolute;
font-family: 'UsualRegular', sans-serif;
font-size: 10px;
font-weight: 400;
text-transform: uppercase;
left: 12px;
top: 4px;
&-dark {
color: $form-label-dark;
}
}

.hidden {
display: none;
}

.input-vertical-error {
font-family: 'UsualRegular', sans-serif;
font-size: 12px;
font-weight: 400;
line-height: 16px;
letter-spacing: 0em;
text-align: left;
color: $form-error-red;
margin-left: 15px;
margin-top: 5px;
}

.error-icon {
position: absolute;
right: 20px;
top: 12px;
color: $form-error-red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/* @format */
import { Shallow } from 'shallow-render';
import { ComponentsModule } from '../../components.module';
import { FormInputComponent, FormInputConfig } from './form-input.component';
import { ReactiveFormsModule, UntypedFormControl } from '@angular/forms';

type AttrOnOff = 'on' | 'off';

describe('FormInputComponent', () => {
let shallow: Shallow<FormInputComponent>;

beforeEach(async () => {
shallow = new Shallow(FormInputComponent, ComponentsModule).import(
ReactiveFormsModule
);
});

it('should create with an empty control', async () => {
const mockControl = new UntypedFormControl('');
const { instance } = await shallow.render({
bind: { control: mockControl },
});

expect(instance).toBeTruthy();
});

it('should create with a control', async () => {
const mockControl = new UntypedFormControl('input');
const { instance } = await shallow.render({
bind: { control: mockControl },
});

expect(instance).toBeTruthy();
});

it('should bind the input type with empty form control', async () => {
const mockControl = new UntypedFormControl('');
const { find } = await shallow.render({
bind: { type: 'password', control: mockControl },
});

const inputElement = find('input').nativeElement;

expect(inputElement.type).toBe('password');
});

it('should bind the input type with form control', async () => {
const control = new UntypedFormControl('input');
const { find } = await shallow.render({
bind: { control: control, type: 'password' },
});

const inputElement = find('input').nativeElement;

expect(inputElement.type).toBe('password');
});

it('should hide label for empty number inputs', async () => {
const mockControl = new UntypedFormControl('');

const { instance } = await shallow.render({
bind: { type: 'number', control: mockControl },
});

expect(instance.isLabelHidden()).toBeTrue();
});

it('should show label for non-empty text inputs', async () => {
const mockControl = new UntypedFormControl('Some text');

const { instance } = await shallow.render({
bind: { type: 'text', control: mockControl },
});

expect(instance.isLabelHidden()).toBeFalse();
});

it('should set input attributes based on config', async () => {
const mockControl = new UntypedFormControl('Some text');

const config: FormInputConfig = {
autocorrect: 'off',
autocapitalize: 'off',
spellcheck: 'off',
};
const { find } = await shallow.render({
bind: { config, control: mockControl },
});

const inputElement = find('input').nativeElement;

expect(inputElement.getAttribute('autocorrect')).toBe(config.autocorrect);

expect(inputElement.getAttribute('autocapitalize')).toBe(
config.autocapitalize
);

expect(inputElement.getAttribute('spellcheck')).toBe(config.spellcheck);
});

it('should emit valueChange event on input value change', async () => {
const mockControl = new UntypedFormControl('');

const mockValue = 'test value';
const { instance, find } = await shallow.render({
bind: { control: mockControl },
});

const spy = spyOn(instance.valueChangeSubject, 'next');

instance.onInputChange(mockValue);

expect(spy).toHaveBeenCalledWith(mockValue);
});

it('should apply right-align class based on config', async () => {
const mockControl = new UntypedFormControl('');
const { instance, fixture } = await shallow.render({
bind: { config: { textAlign: 'right' }, control: mockControl },
});
fixture.detectChanges();

expect(instance.rightAlign).toBeTrue();
});
it('should bind the placeholder attribute to the input element', async () => {
const mockControl = new UntypedFormControl('');
const placeholderValue = 'Enter text here';
const { find } = await shallow.render({
bind: { placeholder: placeholderValue, control: mockControl },
});

const inputElement = find('input').nativeElement;

expect(inputElement.placeholder).toBe(placeholderValue);
});

it('should return the correct error from the validation array', async () => {
const mockControl = new UntypedFormControl('');
const { instance } = await shallow.render({
bind: {
validators: [
{
validation: 'minLength',
message: 'Must be at least 3 characters',
value: 3,
},
{
validation: 'maxLength',
message: 'Must be at most 10 characters',
value: 10,
},
],
control: mockControl,
},
});

const errorMessage = instance.getInputErrorFromValue('aa')

expect(errorMessage).toBe('Must be at least 3 characters');
});
});
Loading

0 comments on commit 9c92e7c

Please sign in to comment.