diff --git a/demo/src/app/components/+dropdown/demos/index.ts b/demo/src/app/components/+dropdown/demos/index.ts index 9112424d97..be71ab2e25 100644 --- a/demo/src/app/components/+dropdown/demos/index.ts +++ b/demo/src/app/components/+dropdown/demos/index.ts @@ -17,6 +17,7 @@ import { DemoDropdownAutoCloseComponent } from './autoclose/autoclose'; import { DemoDropdownCustomHtmlComponent } from './custom-html/custom-html'; import { DemoAccessibilityComponent } from './accessibility/accessibility'; import { DemoDropdownByIsOpenPropComponent } from './trigger-by-isopen-property/trigger-by-isopen-property'; +import { DemoDropdownInsideClickComponent } from './inside-click/inside-click'; export const DEMO_COMPONENTS = [ DemoDropdownBasicComponent, @@ -37,5 +38,6 @@ export const DEMO_COMPONENTS = [ DemoDropdownStateChangeEventComponent, DemoDropdownAutoCloseComponent, DemoDropdownCustomHtmlComponent, - DemoAccessibilityComponent + DemoAccessibilityComponent, + DemoDropdownInsideClickComponent ]; diff --git a/demo/src/app/components/+dropdown/demos/inside-click/inside-click.html b/demo/src/app/components/+dropdown/demos/inside-click/inside-click.html new file mode 100644 index 0000000000..f61699c3ce --- /dev/null +++ b/demo/src/app/components/+dropdown/demos/inside-click/inside-click.html @@ -0,0 +1,13 @@ +
+ + +
diff --git a/demo/src/app/components/+dropdown/demos/inside-click/inside-click.ts b/demo/src/app/components/+dropdown/demos/inside-click/inside-click.ts new file mode 100644 index 0000000000..e32e360fb6 --- /dev/null +++ b/demo/src/app/components/+dropdown/demos/inside-click/inside-click.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'demo-dropdown-inside-click', + templateUrl: './inside-click.html' +}) +export class DemoDropdownInsideClickComponent {} diff --git a/demo/src/app/components/+dropdown/dropdown-section.list.ts b/demo/src/app/components/+dropdown/dropdown-section.list.ts index 6768a42692..1f63222461 100644 --- a/demo/src/app/components/+dropdown/dropdown-section.list.ts +++ b/demo/src/app/components/+dropdown/dropdown-section.list.ts @@ -17,6 +17,7 @@ import { DemoDropdownStateChangeEventComponent } from './demos/state-change-even import { DemoDropdownAutoCloseComponent } from './demos/autoclose/autoclose'; import { DemoDropdownCustomHtmlComponent } from './demos/custom-html/custom-html'; import { DemoAccessibilityComponent } from './demos/accessibility/accessibility'; +import { DemoDropdownInsideClickComponent } from './demos/inside-click/inside-click'; import { ContentSection } from '../../docs/models/content-section.model'; import { DemoTopSectionComponent } from '../../docs/demo-section-components/demo-top-section/index'; @@ -120,6 +121,15 @@ export const demoComponentContent: ContentSection[] = [ to right align the dropdown menu.

`, outlet: DemoDropdownAlignmentComponent }, + { + title: 'Inside click', + anchor: 'inside-click', + component: require('!!raw-loader?lang=typescript!./demos/inside-click/inside-click.ts'), + html: require('!!raw-loader?lang=markup!./demos/inside-click/inside-click.html'), + description: `

By default, a dropdown menu closes on document click, even if you clicked on an element inside the dropdown. + Use [insideClick]="true" to allow click inside the dropdown

`, + outlet: DemoDropdownInsideClickComponent + }, { title: 'Nested dropdowns (experimental)', anchor: 'nested-dropdowns', diff --git a/src/dropdown/bs-dropdown-container.component.ts b/src/dropdown/bs-dropdown-container.component.ts index 79be5cf0c4..f927decf0d 100644 --- a/src/dropdown/bs-dropdown-container.component.ts +++ b/src/dropdown/bs-dropdown-container.component.ts @@ -36,11 +36,11 @@ export class BsDropdownContainerComponent implements OnDestroy { private _state: BsDropdownState, private cd: ChangeDetectorRef, private _renderer: Renderer2, - _element: ElementRef + private _element: ElementRef ) { this._subscription = _state.isOpenChange.subscribe((value: boolean) => { this.isOpen = value; - const dropdown = _element.nativeElement.querySelector('.dropdown-menu'); + const dropdown = this._element.nativeElement.querySelector('.dropdown-menu'); if (dropdown && !isBs3()) { this._renderer.addClass(dropdown, 'show'); if (dropdown.classList.contains('dropdown-menu-right')) { @@ -61,6 +61,11 @@ export class BsDropdownContainerComponent implements OnDestroy { }); } + /** @internal */ + _contains(el: Element): boolean { + return this._element.nativeElement.contains(el); + } + ngOnDestroy(): void { this._subscription.unsubscribe(); } diff --git a/src/dropdown/bs-dropdown-toggle.directive.ts b/src/dropdown/bs-dropdown-toggle.directive.ts index 3a16dfd626..7999e375ad 100644 --- a/src/dropdown/bs-dropdown-toggle.directive.ts +++ b/src/dropdown/bs-dropdown-toggle.directive.ts @@ -8,6 +8,7 @@ import { import { Subscription } from 'rxjs'; import { BsDropdownState } from './bs-dropdown.state'; +import { BsDropdownDirective } from './bs-dropdown.directive'; @Directive({ selector: '[bsDropdownToggle],[dropdownToggle]', @@ -24,7 +25,7 @@ export class BsDropdownToggleDirective implements OnDestroy { private _subscriptions: Subscription[] = []; - constructor(private _state: BsDropdownState, private _element: ElementRef) { + constructor(private _state: BsDropdownState, private _element: ElementRef, private dropdown: BsDropdownDirective) { // sync is open value with state this._subscriptions.push( this._state.isOpenChange.subscribe( @@ -52,7 +53,8 @@ export class BsDropdownToggleDirective implements OnDestroy { if ( this._state.autoClose && event.button !== 2 && - !this._element.nativeElement.contains(event.target) + !this._element.nativeElement.contains(event.target) && + !(this.dropdown.insideClick && this.dropdown._contains(event)) ) { this._state.toggleClick.emit(false); } diff --git a/src/dropdown/bs-dropdown.directive.ts b/src/dropdown/bs-dropdown.directive.ts index a49c5b2698..e60c9716b8 100644 --- a/src/dropdown/bs-dropdown.directive.ts +++ b/src/dropdown/bs-dropdown.directive.ts @@ -65,6 +65,11 @@ export class BsDropdownDirective implements OnInit, OnDestroy { return this._state.autoClose; } + /** + * This attribute indicates that the dropdown shouldn't close on inside click when autoClose is set to true + */ + @Input() insideClick: boolean; + /** * Disables dropdown toggle and hides dropdown menu if opened */ @@ -120,17 +125,17 @@ export class BsDropdownDirective implements OnInit, OnDestroy { return !isBs3(); } - // todo: move to component loader - private _isInlineOpen = false; + private _dropdown: ComponentLoader; private get _showInline(): boolean { return !this.container; } - private _inlinedMenu: EmbeddedViewRef; + // todo: move to component loader + private _isInlineOpen = false; + private _inlinedMenu: EmbeddedViewRef; private _isDisabled: boolean; - private _dropdown: ComponentLoader; private _subscriptions: Subscription[] = []; private _isInited = false; @@ -280,6 +285,12 @@ export class BsDropdownDirective implements OnInit, OnDestroy { return this.show(); } + /** @internal */ + _contains(event: any): boolean { + return this._elementRef.nativeElement.contains(event.target) || + (this._dropdown.instance && this._dropdown.instance._contains(event.target)); + } + ngOnDestroy(): void { // clean up subscriptions and destroy dropdown for (const sub of this._subscriptions) { diff --git a/src/spec/accordion.component.spec.ts b/src/spec/accordion.component.spec.ts index 636f3c6155..2a60f2a48b 100644 --- a/src/spec/accordion.component.spec.ts +++ b/src/spec/accordion.component.spec.ts @@ -126,7 +126,7 @@ describe('Component: Accordion', () => { it('should have the appropriate heading', () => { const titles = Array.from( - element.querySelectorAll('.panel-heading .accordion-toggle div') + element.querySelectorAll('.panel-heading .accordion-toggle button') ); titles.forEach((title: HTMLElement, idx: number) => { const expectedTitle = `Panel ${idx + 1}`; diff --git a/src/spec/bs-dropdown-container.component.spec.ts b/src/spec/bs-dropdown-container.component.spec.ts new file mode 100644 index 0000000000..a9a00b2f50 --- /dev/null +++ b/src/spec/bs-dropdown-container.component.spec.ts @@ -0,0 +1,45 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BsDropdownContainerComponent, BsDropdownModule, BsDropdownState } from '../dropdown'; +import { Subject } from 'rxjs'; +import { window } from '../utils'; + +describe('BsDropdownContainerComponent tests', () => { + let fixture: ComponentFixture; + let component: BsDropdownContainerComponent; + /* tslint:disable-next-line:no-inferred-empty-object-type */ + const stateSubject = new Subject(); + let fakeService; + + beforeEach(() => { + fakeService = { + isOpenChange: stateSubject.asObservable() + }; + TestBed.configureTestingModule({ + imports: [BsDropdownModule.forRoot()], + providers: [{ provide: BsDropdownState, useValue: fakeService }] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BsDropdownContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should not be null', () => { + expect(component).not.toBeNull(); + }); + + it('should be call isOpenChange method', () => { + const tempVal = window.__theme; + window.__theme = 'bs4'; + const spy = spyOn((component as any).cd, 'detectChanges'); + + stateSubject.next(true); + fixture.detectChanges(); + + expect(spy).toHaveBeenCalled(); + window.__theme = tempVal; + }); +}); diff --git a/src/spec/bs-dropdown.directive.spec.ts b/src/spec/bs-dropdown.directive.spec.ts index f46e58f284..fff59f65d5 100644 --- a/src/spec/bs-dropdown.directive.spec.ts +++ b/src/spec/bs-dropdown.directive.spec.ts @@ -1,8 +1,10 @@ /* tslint:disable:max-file-line-count */ -import { Component } from '@angular/core'; -import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { Component, DebugElement } from '@angular/core'; +import { async, fakeAsync, TestBed, tick, ComponentFixture } from '@angular/core/testing'; -import { BsDropdownConfig, BsDropdownModule } from '../dropdown'; +import { BsDropdownConfig, BsDropdownDirective, BsDropdownModule } from '../dropdown'; +import { window } from '../utils/facade/browser'; +import { By } from '@angular/platform-browser'; @Component({ selector: 'dropdown-test', @@ -10,10 +12,12 @@ import { BsDropdownConfig, BsDropdownModule } from '../dropdown'; }) class TestDropdownComponent { isOpen: Boolean = false; + dropup: Boolean = false; isDisabled: Boolean = false; - addToggleClass: Boolean = false; - autoClose = false; - keyboardNav: Boolean = false; + autoClose: Boolean = true; + isOpenChangeValue: Boolean = false; + insideClick: Boolean = false; + container: String = ''; constructor(config: BsDropdownConfig) { Object.assign(this, config); @@ -21,27 +25,28 @@ class TestDropdownComponent { } const defaultHtml = ` -
- - -
-`; - -const htmlWithBinding = ` -
- +
+
+

Title outside dropdown

`; describe('Directive: Dropdown', () => { - it('should be closed by default', () => { + let fixture: ComponentFixture; + let element: HTMLElement; + let context: TestDropdownComponent; + let directive: BsDropdownDirective; + beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TestDropdownComponent], imports: [BsDropdownModule.forRoot()] @@ -49,24 +54,31 @@ describe('Directive: Dropdown', () => { TestBed.overrideComponent(TestDropdownComponent, { set: {template: defaultHtml} }); - const fixture = TestBed.createComponent(TestDropdownComponent); + })); + beforeEach(() => { + fixture = TestBed.createComponent(TestDropdownComponent); + element = fixture.nativeElement; + context = fixture.componentInstance; + // get the typeahead directive instance + const inputs = fixture.debugElement.queryAll( + By.directive(BsDropdownDirective) + ); + directive = inputs.map( + (de: DebugElement) => + de.injector.get(BsDropdownDirective) + )[0]; fixture.detectChanges(); - const element = fixture.nativeElement; + }); + + it('should be closed by default', () => { expect(element.querySelector('[dropdown]').classList).not.toContain('open'); }); + it('autoClose value should be true by default', () => { + expect(directive.autoClose).toBeTruthy(); + }); + it('should be opened if isOpen === true and toggle on isOpen changes', () => { - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: htmlWithBinding} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); - fixture.detectChanges(); - const element = fixture.nativeElement; - const context = fixture.componentInstance; context.isOpen = true; fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).toContain('open'); @@ -79,16 +91,6 @@ describe('Directive: Dropdown', () => { }); it('should toggle by click', () => { - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: defaultHtml} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); - fixture.detectChanges(); - const element = fixture.nativeElement; expect(element.querySelector('[dropdown]').classList).not.toContain('open'); element.querySelector('button').click(); fixture.detectChanges(); @@ -99,17 +101,6 @@ describe('Directive: Dropdown', () => { }); it('should be closed if was opened by click and then isOpen === false was set', () => { - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: htmlWithBinding} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); - fixture.detectChanges(); - const element = fixture.nativeElement; - const context = fixture.componentInstance; expect(element.querySelector('[dropdown]').classList).not.toContain('open'); element.querySelector('button').click(); fixture.detectChanges(); @@ -119,275 +110,215 @@ describe('Directive: Dropdown', () => { expect(element.querySelector('[dropdown]').classList).not.toContain('open'); }); - it( - 'should change and update isOpen when it is opened or closed', - fakeAsync(() => { - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: htmlWithBinding} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); - fixture.detectChanges(); - tick(); - const element = fixture.nativeElement; - const context = fixture.componentInstance; - fixture.detectChanges(); - element.querySelector('button').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).toContain('open'); - expect(context.isOpen).toBe(true); - tick(); - element.querySelector('li').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).not.toContain( - 'open' - ); - expect(context.isOpen).toBe(false); - }) - ); + it('should close by click on any element inside the dropdown', fakeAsync(() => { + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + tick(); + element.querySelector('li').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + tick(); + element.querySelector('a').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + })); + + it('should close by click on any element outside the dropdown', () => { + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + element.querySelector('h1').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + }); - it( - 'should close by click on nonInput menu item', - fakeAsync(() => { - const html = ` -
- - -
- `; - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: html} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); - fixture.detectChanges(); - tick(); - const element = fixture.nativeElement; - fixture.detectChanges(); - element.querySelector('button').click(); - fixture.detectChanges(); - tick(); - expect(element.querySelector('[dropdown]').classList).toContain('open'); - element.querySelector('li').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).not.toContain( - 'open' - ); - }) - ); + it('should be opened if isOpen === true and toggle on isOpen changes', () => { + context.isOpen = true; + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + context.isOpen = false; + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + }); - xit( - 'should not close by click on input or textarea menu item', - fakeAsync(() => { - const html = ` -
- -
    -
  • -
  • -
  • Two
  • -
-
- `; - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: html} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); - fixture.detectChanges(); - tick(); - const element = fixture.nativeElement; - fixture.detectChanges(); - element.querySelector('button').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).toContain('open'); - element.querySelector('input').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).toContain('open'); - element.querySelector('textarea').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).toContain('open'); - }) - ); + it('should change and update isOpen when it is opened or closed', fakeAsync(() => { + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + expect(context.isOpen).toBe(true); + tick(); + element.querySelector('li').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain( + 'open' + ); + expect(context.isOpen).toBe(false); + })); - it( - 'should not close by click on menu item if autoClose === false', - fakeAsync(() => { - const html = ` -
- - -
- `; - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: html} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); - fixture.detectChanges(); - const element = fixture.nativeElement; - const context = fixture.componentInstance; - context.autoClose = false; - tick(); - fixture.detectChanges(); - element.querySelector('button').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).toContain('open'); - tick(); - element.querySelector('li').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).toContain('open'); - }) - ); + it('should has class dropup if property dropup equal true', () => { + context.dropup = true; + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('dropup'); + }); - xit('should close by click on input in menu if autoClose === always', () => { - const html = ` -
- - -
- `; - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: html} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); + it('should not open if isDisabled equal true', () => { + context.isDisabled = true; + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + context.isDisabled = false; fixture.detectChanges(); - const element = fixture.nativeElement; - const context = fixture.componentInstance; + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + }); + + it('should close if only dropdown button was clicked if autoClose equal false', fakeAsync(() => { + context.autoClose = false; + fixture.detectChanges(); + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + tick(); + element.querySelector('a').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + element.querySelector('h1').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + })); + + it('should not close by click on menu item if autoClose equal true', fakeAsync(() => { context.autoClose = true; fixture.detectChanges(); element.querySelector('button').click(); fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).toContain('open'); - element.querySelector('input').click(); + tick(); + element.querySelector('li').click(); fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + })); + + it('value isOpenChange emits event', () => { + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + expect(context.isOpenChangeValue).toBeTruthy(); }); - xit('should close by click on any element outside the dropdown', () => { - const html = ` -
- - -
- outside - `; - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: html} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); + it('should close if only dropdown button was clicked if autoClose equal false', fakeAsync(() => { + context.autoClose = false; fixture.detectChanges(); - const element = fixture.nativeElement; - const context = fixture.componentInstance; - context.autoClose = true; + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + tick(); + element.querySelector('a').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + element.querySelector('h1').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + })); + + it('should not close by click on menu item if insideClick equal true', fakeAsync(() => { + context.insideClick = true; fixture.detectChanges(); element.querySelector('button').click(); fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).toContain('open'); + tick(); + element.querySelector('a').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); element.querySelector('li').click(); fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).toContain('open'); - element.querySelector('span').click(); + element.querySelector('h1').click(); fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).not.toContain('open'); - }); + })); - xit( - 'should enable navigation of dropdown list elements with the arrow keys if keyboardNav is true', - () => { - const html = ` -
- - -
- `; - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: html} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); - fixture.detectChanges(); - const element = fixture.nativeElement; - const context = fixture.componentInstance; - context.keyboardNav = true; - fixture.detectChanges(); - element.querySelector('button').click(); - fixture.detectChanges(); - expect(element.querySelector('[dropdown]').classList).toContain('open'); - // todo: emulate keypress, check if item has hover - } - ); -}); -describe('Directive: dropdownToggle', () => { - it('should not open if toggle isDisabled', () => { - const html = ` -
- - -
- `; - TestBed.configureTestingModule({ - declarations: [TestDropdownComponent], - imports: [BsDropdownModule.forRoot()] - }); - TestBed.overrideComponent(TestDropdownComponent, { - set: {template: html} - }); - const fixture = TestBed.createComponent(TestDropdownComponent); + it('should close by click on menu item if insideClick equal false', fakeAsync(() => { + context.insideClick = false; fixture.detectChanges(); - const element = fixture.nativeElement; - const context = fixture.componentInstance; + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + tick(); + element.querySelector('li').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + })); + + it('should close by click on menu item if insideClick equal false', fakeAsync(() => { + context.insideClick = false; + fixture.detectChanges(); + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + tick(); + element.querySelector('li').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + })); + + it('should change aria-expanded property, when dropdown was opened', fakeAsync(() => { + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + expect(element.querySelector('[dropdownToggle]').getAttribute('aria-expanded')).toEqual('true'); + tick(); + element.querySelector('li').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdownToggle]').getAttribute('aria-expanded')).toEqual('false'); + })); + + it('should change disabled property, when dropdown was opened', fakeAsync(() => { context.isDisabled = true; fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).not.toContain('open'); element.querySelector('button').click(); fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).not.toContain('open'); - context.isDisabled = false; + expect(element.querySelector('[dropdownToggle]').getAttribute('disabled')).toEqual('true'); + })); + + it('should open if container is body', () => { + context.container = 'body'; + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + element.querySelector('button').click(); fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).toContain('open'); + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + }); + + it('should open if isBs3 method return true', () => { + const tempVal = window.__theme; + window.__theme = 'bs4'; + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); element.querySelector('button').click(); fixture.detectChanges(); expect(element.querySelector('[dropdown]').classList).toContain('open'); + element.querySelector('button').click(); + fixture.detectChanges(); + expect(element.querySelector('[dropdown]').classList).not.toContain('open'); + window.__theme = tempVal; }); });