Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ddce3f8
create autocomplete component
AnnaAlekhina Jan 13, 2020
b2d9325
add mdx storybook documentation
AnnaAlekhina Jan 13, 2020
e8012dc
add simple autocomplete element
AnnaAlekhina Jan 13, 2020
c4564ad
create autocomplete form wrapper
AnnaAlekhina Jan 13, 2020
7719622
create custom value accessor
AnnaAlekhina Jan 13, 2020
ec446cd
wrapper story file united with autocomplete story file
AnnaAlekhina Jan 14, 2020
2c7b82a
add inputs and outputs to component
AnnaAlekhina Jan 14, 2020
dd79d31
add filter options method
AnnaAlekhina Jan 14, 2020
1ebf0ee
create two new folders for forms
AnnaAlekhina Jan 14, 2020
96629e8
add autocomplete service and loader
AnnaAlekhina Jan 14, 2020
3f7bc42
fix getOptions method
AnnaAlekhina Jan 14, 2020
5245583
add new custom form component
AnnaAlekhina Jan 15, 2020
1557438
create custom options
AnnaAlekhina Jan 16, 2020
85e9dee
fix custom options and add default options
AnnaAlekhina Jan 16, 2020
60f8a67
add field foto to custom form
AnnaAlekhina Jan 16, 2020
8f1717a
add subject to service form component
AnnaAlekhina Jan 16, 2020
8a4ece9
add unsubscribe without external package
AnnaAlekhina Jan 16, 2020
a0965f3
add InjectionToken to provide
AnnaAlekhina Jan 16, 2020
a6079e0
transfer Inject logic to autocomplete component
AnnaAlekhina Jan 16, 2020
608b89d
fix underfined inputChild bag
AnnaAlekhina Jan 17, 2020
66ef829
add modifiers and change debaunce and delay time
AnnaAlekhina Jan 17, 2020
0e94898
fix documentation
AnnaAlekhina Jan 17, 2020
45e9ffc
write documentation and examples for autocomplete
AnnaAlekhina Jan 17, 2020
cebae53
add ngModel component
AnnaAlekhina Jan 20, 2020
f76456c
add tests
AnnaAlekhina Jan 21, 2020
7e8ad5c
fix text headings and create text constants
AnnaAlekhina Jan 22, 2020
5081a25
add template options test
AnnaAlekhina Jan 22, 2020
a298abc
Fix autocomplete dropdown position
feerzlay Jan 27, 2020
0136f31
Add public api
feerzlay Feb 25, 2020
30ae05f
Clean some code
feerzlay Feb 26, 2020
24e5ac0
Fix autocomplete label float
feerzlay Mar 16, 2020
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
2 changes: 1 addition & 1 deletion jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
import '@testing-library/jest-dom';
1 change: 1 addition & 0 deletions projects/elonkit/src/public-api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './ui/autocomplete';
export * from './ui/counter';
export * from './ui/inline-form-field';
export * from './ui/timepicker';
236 changes: 236 additions & 0 deletions projects/elonkit/src/ui/autocomplete/__specs__/autocomplete.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { Component } from '@angular/core';
import { inject, fakeAsync, tick } from '@angular/core/testing';

import { FormsModule } from '@angular/forms';

import { OverlayContainer } from '@angular/cdk/overlay';
import { MatFormFieldModule } from '@angular/material/form-field';

import { render, RenderResult } from '@testing-library/angular';

import { AutocompleteModule, AutocompleteComponent } from '..';

@Component({
template: `
<mat-form-field appearance="outline" class="es-autocomplete-story-custom">
<mat-label>Friend</mat-label>
<es-autocomplete
[(ngModel)]="text"
[options]="options"
[valueFn]="valueFn"
(changeText)="onChangeText($event)"
>
<ng-container *esAutocompleteOption="let option">
<img class="es-autocomplete-story-custom__option-img" [src]="option.photo" />
{{ option.name }}
</ng-container>
</es-autocomplete>
</mat-form-field>
`
})
class AutocompleteCustomComponent {
public text = '';
public options: any[] = FRIENDS;
public valueFn(option: any): any {
return option.name;
}
}

const FRUITS = ['Apple', 'Lemon', 'Mango'];
const FRIENDS = [
{
name: 'Anna',
photo: 'https://joeschmoe.io/api/v1/jenni'
},
{
name: 'Mary',
photo: 'https://joeschmoe.io/api/v1/julie'
}
];

describe('Autocomplete', () => {
describe('Base', () => {
let component: RenderResult<AutocompleteComponent, AutocompleteComponent>;
let overlay: OverlayContainer;
let overlayElement: HTMLElement;

beforeEach(async () => {
component = await render(AutocompleteComponent, {
imports: [AutocompleteModule],
componentProperties: {
options: FRUITS
},
excludeComponentDeclaration: true
});

inject([OverlayContainer], (oc: OverlayContainer) => {
overlay = oc;
overlayElement = oc.getContainerElement();
})();
});

afterEach(inject([OverlayContainer], (currentOverlay: OverlayContainer) => {
currentOverlay.ngOnDestroy();
overlay.ngOnDestroy();
}));

it('Should display passed options', fakeAsync(async () => {
const input = component.getByTestId('input');

component.focusIn(input);

const options = overlayElement.querySelectorAll('[data-testid="mat-option"]');
expect(options).toHaveLength(3);

// all items of FRUITS array contain in overlay
for (const fruit of FRUITS) {
expect(overlayElement.textContent).toContain(fruit);
}
}));

it('Should change value on input text in view of debounce time', fakeAsync(async () => {
const TEXT_FOO = 'Foo';
const TEXT_BAR = 'Bar';
const onChangeText = jest.fn();
component.fixture.componentInstance.changeText.emit = onChangeText;

const input = component.getByTestId('input');

component.input(input, { target: { value: TEXT_FOO } });

tick();
expect(onChangeText).toBeCalledTimes(1);
expect(onChangeText).toBeCalledWith(TEXT_FOO);

component.fixture.componentInstance.debounceTime = 100;
component.fixture.componentInstance.changeDetector.detectChanges();

component.input(input, { target: { value: TEXT_BAR } });

tick(99);
expect(onChangeText).toBeCalledTimes(1);

tick(1);
expect(onChangeText).toBeCalledTimes(2);
expect(onChangeText).toBeCalledWith(TEXT_BAR);
}));

it('Should display entered text any kind', fakeAsync(async () => {
const TEXT_BAZ = 'Baz';
const TEXT_APPLE_SMTH = 'Apple232323';

component.fixture.componentInstance.freeInput = true;
component.fixture.componentInstance.changeDetector.detectChanges();

const input = component.getByTestId('input');

// if user did not choose some option
component.focusIn(input);
tick();

component.input(input, { target: { value: TEXT_BAZ } });
tick();

component.blur(input);
tick();

expect(component.getByDisplayValue(TEXT_BAZ));

// if user choose some option
component.focusIn(input);
tick();

const options = overlayElement.querySelectorAll('[data-testid="mat-option"]');
component.click(options[0]);
tick();

component.input(input, { target: { value: TEXT_APPLE_SMTH } });
tick();

component.blur(input);
tick();

expect(component.getByDisplayValue(TEXT_APPLE_SMTH));
}));

it('Should display entered text only from options', fakeAsync(async () => {
const TEXT_BAZ = 'Baz';
const TEXT_APPLE = 'Apple';
const TEXT_APPLE_SMTH = 'Apple232323';

component.fixture.componentInstance.freeInput = false;
component.fixture.componentInstance.changeDetector.detectChanges();

const input = component.getByTestId('input');

// if user did not choose some option
component.focusIn(input);
tick();

component.input(input, { target: { value: TEXT_BAZ } });
tick();

component.blur(input);
tick();

expect(component.getByDisplayValue(''));

// if user choose some option
component.focusIn(input);
tick();

const options = overlayElement.querySelectorAll('[data-testid="mat-option"]');
component.click(options[0]);
tick();

component.input(input, { target: { value: TEXT_APPLE_SMTH } });
tick();

component.blur(input);
tick();

expect(component.getByDisplayValue(TEXT_APPLE));
}));
});

describe('Custom options', () => {
let component: RenderResult<AutocompleteCustomComponent, AutocompleteCustomComponent>;
let overlay: OverlayContainer;
let overlayElement: HTMLElement;

beforeEach(async () => {
component = await render(AutocompleteCustomComponent, {
imports: [AutocompleteModule, FormsModule, MatFormFieldModule],
componentProperties: {
options: FRIENDS
}
});

inject([OverlayContainer], (oc: OverlayContainer) => {
overlay = oc;
overlayElement = oc.getContainerElement();
})();
});

afterEach(inject([OverlayContainer], (currentOverlay: OverlayContainer) => {
currentOverlay.ngOnDestroy();
overlay.ngOnDestroy();
}));

it('Should display options with photo and name', fakeAsync(async () => {
const input = component.getByTestId('input');

component.focusIn(input);

const options = overlayElement.querySelectorAll('[data-testid="mat-option"]');

expect(options).toHaveLength(2);

for (let i = 0; i < FRIENDS.length; i++) {
const image = options[i].querySelector('img');
expect(options[i].textContent).toContain(FRIENDS[i].name);
expect(image).toHaveAttribute('src', FRIENDS[i].photo);
}
}));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<form [formGroup]="form">
<mat-form-field appearance="outline" class="es-autocomplete-story-custom">
<mat-label>Friend</mat-label>
<es-autocomplete
formControlName="autocomplete"
[options]="options"
[valueFn]="valueFn"
(changeText)="onChangeText($event)"
>
<ng-container *esAutocompleteOption="let option">
<img class="es-autocomplete-story-custom__option-img" [src]="option.photo" />
{{ option.name }}
<b>{{ option.id }}</b>
</ng-container>
</es-autocomplete>
</mat-form-field>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.es-autocomplete-story-custom {
&__option-img {
height: 25px;
margin-right: 8px;
vertical-align: middle;
}

&.mat-form-field {
width: 100%;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Component, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { GetFilterOptionsByKey } from '../../filter-options';

const OPTIONS = [
{
id: 1,
name: 'Anna',
photo: 'https://joeschmoe.io/api/v1/jenni'
},
{
id: 2,
name: 'Mary',
photo: 'https://joeschmoe.io/api/v1/julie'
},
{
id: 3,
name: 'Elena',
photo: 'https://joeschmoe.io/api/v1/jolee'
}
];

@Component({
selector: 'es-autocomplete-story-custom',
templateUrl: './autocomplete-story-custom.component.html',
styleUrls: ['./autocomplete-story-custom.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class AutocompleteStoryCustomComponent {
public form: FormGroup;
public options: any[] = OPTIONS;

constructor(private formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
autocomplete: ''
});
}

public onChangeText(text: string) {
this.options = GetFilterOptionsByKey(text, OPTIONS, 'name');
}

public valueFn(option: any): any {
return option.name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';

import { AutocompleteModule } from '../../autocomplete.module';

import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';

import { AutocompleteStoryCustomComponent } from './autocomplete-story-custom.component';

@NgModule({
declarations: [AutocompleteStoryCustomComponent],
imports: [
CommonModule,
ReactiveFormsModule,
AutocompleteModule,
MatFormFieldModule,
MatButtonModule
],
exports: [AutocompleteStoryCustomComponent]
})
export class AutocompleteStoryCustomModule {}
Loading