Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 22 additions & 20 deletions src/app/shared/components/form-input/form-input.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,28 @@
/>
}
@if (type === 'select') {
<select
class="form-control input-vertical"
[ngClass]="{ placeholder: !control.value }"
[id]="fieldName"
[name]="fieldName"
[formControl]="control"
>
@for (option of selectOptions; track option) {
<option [value]="option.value">
{{ option.text }}
</option>
}
</select>
}
@if (type === 'select') {
<div
class="input-vertical-select-placeholder"
[class.selected]="control.value"
>
{{ getOptionTextFromValue(control.value) || placeholder }}
<div class="select-wrapper">
<select
class="form-control input-vertical real-select"
[ngClass]="{ placeholder: !control.value }"
[id]="fieldName"
[name]="fieldName"
[formControl]="control"
>
@for (option of selectOptions; track option) {
<option [value]="option.value">
{{ option.text }}
</option>
}
</select>
<label
class="open-toggle input-vertical-select-placeholder"
[attr.for]="fieldName"
class.selected]="control.value"
(click)="openSelect()"
>
{{ getOptionTextFromValue(control.value) || placeholder }}
</label>
</div>
}
<div class="input-vertical-error" [ngClass]="{ hidden: !errors }">
Expand Down
38 changes: 38 additions & 0 deletions src/app/shared/components/form-input/form-input.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,41 @@
cursor: not-allowed;
user-select: none;
}

.select-wrapper {
position: relative;
width: 100%;
border: 1px solid #ced4da;
padding: 10px 0px;
}

.real-select {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
z-index: 2;
cursor: pointer;
}

.input-vertical-select-placeholder {
position: relative;
z-index: 1;
}

.select-wrapper {
position: relative;
width: 100%;
}

.styled-select {
width: 100%;
cursor: pointer;
}

.open-toggle {
cursor: pointer;
top: 24px;
}
106 changes: 106 additions & 0 deletions src/app/shared/components/form-input/form-input.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import {
FormsModule,
ReactiveFormsModule,
UntypedFormControl,
} from '@angular/forms';
import { By } from '@angular/platform-browser';
import {
FormInputComponent,
FormInputSelectOption,
} from './form-input.component';

describe('FormInputComponent', () => {
let component: FormInputComponent;
let fixture: ComponentFixture<FormInputComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [FormInputComponent],
imports: [FormsModule, ReactiveFormsModule],
}).compileComponents();

fixture = TestBed.createComponent(FormInputComponent);
component = fixture.componentInstance;
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should render a text input when type is not "select"', () => {
component.type = 'text';
component.fieldName = 'username';
component.placeholder = 'Enter username';
component.control = new UntypedFormControl('');
fixture.detectChanges();

const inputEl = fixture.debugElement.query(By.css('input.form-control'));

expect(inputEl).toBeTruthy();
expect(inputEl.nativeElement.placeholder).toBe('Enter username');
});

it('should render a select when type is "select"', () => {
component.type = 'select';
component.fieldName = 'country';
component.placeholder = 'Select country';
component.selectOptions = [
{ value: 'us', text: 'USA' },
{ value: 'ca', text: 'Canada' },
];
component.control = new UntypedFormControl('');
fixture.detectChanges();

const selectEl = fixture.debugElement.query(By.css('select'));

expect(selectEl).toBeTruthy();
const options = selectEl.queryAll(By.css('option'));

expect(options.length).toBe(2);
});

it('should toggle openStatus when openSelect() is called', () => {
expect(component.openStatus).toBeFalse();
component.openSelect();

expect(component.openStatus).toBeTrue();
component.openSelect();

expect(component.openStatus).toBeFalse();
});

it('should reset openStatus and alert when handleChange() is called', () => {
spyOn(window, 'alert');
component.openStatus = true;
component.handleChange();

expect(component.openStatus).toBeFalse();
expect(window.alert).toHaveBeenCalledWith('Element selected... closed');
});

it('should display the correct option text from value', () => {
const options: FormInputSelectOption[] = [
{ text: 'Apple', value: 'a' },
{ text: 'Banana', value: 'b' },
];
component.selectOptions = options;
const result = component.getOptionTextFromValue('b');

expect(result).toBe('Banana');
});

it('should hide label if value is empty and type is number', () => {
component.type = 'number';
component.control = new UntypedFormControl('');

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

it('should not hide label if type is date', () => {
component.type = 'date';
component.control = new UntypedFormControl('');

expect(component.isLabelHidden()).toBeFalse();
});
});
9 changes: 9 additions & 0 deletions src/app/shared/components/form-input/form-input.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export class FormInputComponent implements OnInit, AfterViewInit {
@HostBinding('class.right-align') rightAlign = false;
@HostBinding('class.input-vertical') inputVertical = true;

openStatus = false;

@Input() config: FormInputConfig;

constructor(private element: ElementRef) {}
Expand Down Expand Up @@ -125,4 +127,11 @@ export class FormInputComponent implements OnInit, AfterViewInit {
getOptionTextFromValue(value: string) {
return find(this.selectOptions, { value })?.text;
}
openSelect() {
this.openStatus = !this.openStatus;
}

handleChange() {
this.openStatus = false;
}
}
Loading