Skip to content

Commit

Permalink
fix(select): mark touched on hide trigger only when select is open (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
yggg committed Aug 27, 2019
1 parent a01ec7a commit 8295c32
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 22 deletions.
7 changes: 5 additions & 2 deletions src/framework/theme/components/cdk/a11y/a11y.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { ModuleWithProviders, NgModule } from '@angular/core';

import { NbFocusTrapFactoryService } from './focus-trap';

import { NbFocusKeyManagerFactoryService } from './focus-key-manager';

@NgModule({})
export class NbA11yModule {
static forRoot() {
return <ModuleWithProviders>{
ngModule: NbA11yModule,
providers: [NbFocusTrapFactoryService],
providers: [
NbFocusTrapFactoryService,
NbFocusKeyManagerFactoryService,
],
};
}
}
7 changes: 7 additions & 0 deletions src/framework/theme/components/cdk/a11y/focus-key-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { QueryList } from '@angular/core';
import { FocusableOption, FocusKeyManager } from '@angular/cdk/a11y';

export type NbFocusableOption = FocusableOption;
export class NbFocusKeyManager<T> extends FocusKeyManager<T> {}

export class NbFocusKeyManagerFactoryService<T extends NbFocusableOption> {
create(items: QueryList<T> | T[]): NbFocusKeyManager<T> {
return new NbFocusKeyManager<T>(items);
}
}
26 changes: 16 additions & 10 deletions src/framework/theme/components/select/select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
import { NbOverlayRef, NbPortalDirective, NbScrollStrategy } from '../cdk/overlay/mapping';
import { NbOverlayService } from '../cdk/overlay/overlay-service';
import { NbTrigger, NbTriggerStrategy, NbTriggerStrategyBuilderService } from '../cdk/overlay/overlay-trigger';
import { NbFocusKeyManager } from '../cdk/a11y/focus-key-manager';
import { NbFocusKeyManager, NbFocusKeyManagerFactoryService } from '../cdk/a11y/focus-key-manager';
import { ESCAPE } from '../cdk/keycodes/keycodes';
import { NbComponentSize } from '../component-size';
import { NbComponentShape } from '../component-shape';
Expand Down Expand Up @@ -637,7 +637,8 @@ export class NbSelectComponent<T> implements AfterViewInit, AfterContentInit, On
protected hostRef: ElementRef<HTMLElement>,
protected positionBuilder: NbPositionBuilderService,
protected triggerStrategyBuilder: NbTriggerStrategyBuilderService,
protected cd: ChangeDetectorRef) {
protected cd: ChangeDetectorRef,
protected focusKeyManagerFactoryService: NbFocusKeyManagerFactoryService<NbOptionComponent<T>>) {
}

/**
Expand Down Expand Up @@ -862,7 +863,7 @@ export class NbSelectComponent<T> implements AfterViewInit, AfterContentInit, On
}

protected createKeyManager(): void {
this.keyManager = new NbFocusKeyManager<NbOptionComponent<T>>(this.options).withTypeAhead(200);
this.keyManager = this.focusKeyManagerFactoryService.create(this.options).withTypeAhead(200);
}

protected createPositionStrategy(): NbAdjustableConnectedPositionStrategy {
Expand All @@ -887,12 +888,14 @@ export class NbSelectComponent<T> implements AfterViewInit, AfterContentInit, On

protected subscribeOnTriggers() {
this.triggerStrategy.show$.subscribe(() => this.show());
this.triggerStrategy.hide$.subscribe(($event: Event) => {
this.hide();
if (!this.isClickedWithinComponent($event)) {
this.onTouched();
}
});
this.triggerStrategy.hide$
.pipe(filter(() => this.isOpen))
.subscribe(($event: Event) => {
this.hide();
if (!this.isClickedWithinComponent($event)) {
this.onTouched();
}
});
}

protected subscribeOnPositionChange() {
Expand Down Expand Up @@ -938,7 +941,10 @@ export class NbSelectComponent<T> implements AfterViewInit, AfterContentInit, On

this.keyManager.tabOut
.pipe(takeWhile(() => this.alive))
.subscribe(() => this.hide());
.subscribe(() => {
this.hide();
this.onTouched();
});
}

protected getContainer() {
Expand Down
171 changes: 161 additions & 10 deletions src/framework/theme/components/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testi
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { from, zip } from 'rxjs';
import { from, zip, Subject } from 'rxjs';
import createSpy = jasmine.createSpy;

import { NbSelectModule } from './select.module';
import { NbThemeModule } from '../../theme.module';
import { NbOverlayContainerAdapter } from '../cdk/adapter/overlay-container-adapter';
import { NB_DOCUMENT } from '../../theme.options';
import { NbSelectComponent } from './select.component';
import { NbLayoutModule } from '../layout/layout.module';
import { NbOptionComponent } from './option.component';
import { NbOptionGroupComponent } from './option-group.component';
import {
NbSelectModule,
NbThemeModule,
NbOverlayContainerAdapter,
NB_DOCUMENT,
NbSelectComponent,
NbLayoutModule,
NbOptionComponent,
NbOptionGroupComponent,
NbTriggerStrategyBuilderService,
} from '@nebular/theme';
import { NbFocusKeyManagerFactoryService } from '@nebular/theme/components/cdk/a11y/focus-key-manager';

const eventMock = { preventDefault() {} } as Event;

Expand Down Expand Up @@ -99,6 +103,23 @@ export class NbSelectTestComponent {
groups = TEST_GROUPS;
}

@Component({
template: `
<nb-layout>
<nb-layout-column>
<nb-select>
<nb-option value="a">a</nb-option>
<nb-option value="b">b</nb-option>
<nb-option value="c">c</nb-option>
</nb-select>
</nb-layout-column>
</nb-layout>
`,
})
export class BasicSelectTestComponent {}

@Component({
template: `
<nb-layout>
Expand Down Expand Up @@ -581,7 +602,7 @@ describe('Component: NbSelectComponent', () => {
}));

it(`should not call dispose on uninitialized resources`, () => {
const selectFixture = new NbSelectComponent(null, null, null, null, null, null);
const selectFixture = new NbSelectComponent(null, null, null, null, null, null, null);
expect(() => selectFixture.ngOnDestroy()).not.toThrow();
});

Expand Down Expand Up @@ -632,6 +653,33 @@ describe('Component: NbSelectComponent', () => {

expect(selectFixture.componentInstance.isOpen).toBeFalsy();
}));

it('should mark touched when select button loose focus and select closed', fakeAsync(() => {
const touchedSpy = jasmine.createSpy('touched spy');

const selectFixture = TestBed.createComponent(NbSelectComponent);
const selectComponent: NbSelectComponent<any> = selectFixture.componentInstance;
selectFixture.detectChanges();
flush();

selectComponent.registerOnTouched(touchedSpy);
selectFixture.debugElement.query(By.css('.select-button')).triggerEventHandler('blur', {});
expect(touchedSpy).toHaveBeenCalledTimes(1);
}));

it('should not mark touched when select button loose focus and select open', fakeAsync(() => {
const touchedSpy = jasmine.createSpy('touched spy');

const selectFixture = TestBed.createComponent(NbSelectComponent);
const selectComponent: NbSelectComponent<any> = selectFixture.componentInstance;
selectFixture.detectChanges();
flush();

selectComponent.registerOnTouched(touchedSpy);
selectComponent.show();
selectFixture.debugElement.query(By.css('.select-button')).triggerEventHandler('blur', {});
expect(touchedSpy).not.toHaveBeenCalled();
}));
});

describe('NbSelectComponent - falsy values', () => {
Expand Down Expand Up @@ -767,6 +815,109 @@ describe('NbSelectComponent - falsy values', () => {
});
});

describe('NbSelectComponent - Triggers', () => {
let fixture: ComponentFixture<BasicSelectTestComponent>;
let selectComponent: NbSelectComponent<any>;
let triggerBuilderStub;
let showTriggerStub: Subject<Event>;
let hideTriggerStub: Subject<Event>;

beforeEach(fakeAsync(() => {
showTriggerStub = new Subject<Event>();
hideTriggerStub = new Subject<Event>();
triggerBuilderStub = {
trigger() { return this },
host() { return this },
container() { return this },
destroy() {},
build() {
return { show$: showTriggerStub, hide$: hideTriggerStub };
},
};

TestBed.configureTestingModule({
imports: [ RouterTestingModule.withRoutes([]), NbThemeModule.forRoot(), NbLayoutModule, NbSelectModule ],
declarations: [ BasicSelectTestComponent ],
});
TestBed.overrideProvider(NbTriggerStrategyBuilderService, { useValue: triggerBuilderStub });

fixture = TestBed.createComponent(BasicSelectTestComponent);
fixture.detectChanges();
flush();

selectComponent = fixture.debugElement.query(By.directive(NbSelectComponent)).componentInstance;
}));

it('should mark touched if clicked outside of overlay and select', fakeAsync(() => {
const touchedSpy = jasmine.createSpy('touched spy');
selectComponent.registerOnTouched(touchedSpy);

const elementOutsideSelect = fixture.debugElement.query(By.css('nb-layout')).nativeElement;
selectComponent.show();
fixture.detectChanges();

hideTriggerStub.next({ target: elementOutsideSelect } as unknown as Event);

expect(touchedSpy).toHaveBeenCalledTimes(1);
}));

it('should not mark touched if clicked on the select button', fakeAsync(() => {
const touchedSpy = jasmine.createSpy('touched spy');
selectComponent.registerOnTouched(touchedSpy);

const selectButton = fixture.debugElement.query(By.css('.select-button')).nativeElement;
selectComponent.show();
fixture.detectChanges();

hideTriggerStub.next({ target: selectButton } as unknown as Event);

expect(touchedSpy).not.toHaveBeenCalled();
}));
});

describe('NbSelectComponent - Key manager', () => {
let fixture: ComponentFixture<BasicSelectTestComponent>;
let selectComponent: NbSelectComponent<any>;
let tabOutStub: Subject<void>;
let keyManagerFactoryStub;
let keyManagerStub;

beforeEach(fakeAsync(() => {
tabOutStub = new Subject<void>();
keyManagerStub = {
withTypeAhead() { return this; },
setActiveItem() {},
setFirstItemActive() {},
onKeydown() {},
tabOut: tabOutStub,
};
keyManagerFactoryStub = { create() { return keyManagerStub; } };

TestBed.configureTestingModule({
imports: [ RouterTestingModule.withRoutes([]), NbThemeModule.forRoot(), NbLayoutModule, NbSelectModule ],
declarations: [ BasicSelectTestComponent ],
});
TestBed.overrideProvider(NbFocusKeyManagerFactoryService, { useValue: keyManagerFactoryStub });

fixture = TestBed.createComponent(BasicSelectTestComponent);
fixture.detectChanges();
flush();

selectComponent = fixture.debugElement.query(By.directive(NbSelectComponent)).componentInstance;
}));

it('should mark touched when tabbing out from options list', fakeAsync(() => {
selectComponent.show();
fixture.detectChanges();

const touchedSpy = jasmine.createSpy('touched spy');
selectComponent.registerOnTouched(touchedSpy);
tabOutStub.next();
flush();
expect(touchedSpy).toHaveBeenCalledTimes(1);
}));
});

describe('NbOptionComponent', () => {
let fixture: ComponentFixture<NbReactiveFormSelectComponent>;
let testSelectComponent: NbReactiveFormSelectComponent;
Expand Down

0 comments on commit 8295c32

Please sign in to comment.