text:
import {TestBed, async, ComponentFixture, fakeAsync, tick, inject} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {
  Component,
  DebugElement,
  QueryList,
  ViewChild,
  ViewChildren,
  ChangeDetectionStrategy,
  OnInit,
} from '@angular/core';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MdSelectModule} from './index';
import {OverlayContainer} from '../core/overlay/overlay-container';
import {MdSelect, MdSelectFloatPlaceholderType} from './select';
import {MdSelectDynamicMultipleError, MdSelectNonArrayValueError} from './select-errors';
import {MdOption} from '../core/option/option';
import {Dir} from '../core/rtl/dir';
import {DOWN_ARROW, UP_ARROW, ENTER, SPACE} from '../core/keyboard/keycodes';
import {
  ControlValueAccessor, FormControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule
} from '@angular/forms';
import {Subject} from 'rxjs/Subject';
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
import {dispatchFakeEvent, dispatchKeyboardEvent} from '../core/testing/dispatch-events';
import {wrappedErrorMessage} from '../core/testing/wrapped-error-message';
import {TAB} from '../core/keyboard/keycodes';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';

describe('MdSelect', () => {
  let overlayContainerElement: HTMLElement;
  let dir: {value: 'ltr'|'rtl'};
  let scrolledSubject = new Subject();
  let viewportRuler: ViewportRuler;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [MdSelectModule, ReactiveFormsModule, FormsModule, NoopAnimationsModule],
      declarations: [
        BasicSelect,
        NgModelSelect,
        ManySelects,
        NgIfSelect,
        SelectInitWithoutOptions,
        SelectWithChangeEvent,
        CustomSelectAccessor,
        CompWithCustomSelect,
        MultiSelect,
        FloatPlaceholderSelect,
        SelectWithErrorSibling,
        ThrowsErrorOnInit,
        BasicSelectOnPush,
        BasicSelectOnPushPreselected,
        SelectWithPlainTabindex,
        SelectEarlyAccessSibling,
        BasicSelectInitiallyHidden,
        BasicSelectNoPlaceholder,
        BasicSelectWithTheming
      ],
      providers: [
        {provide: OverlayContainer, useFactory: () => {
          overlayContainerElement = document.createElement('div') as HTMLElement;
          overlayContainerElement.classList.add('cdk-overlay-container');

          document.body.appendChild(overlayContainerElement);

          // remove body padding to keep consistent cross-browser
          document.body.style.padding = '0';
          document.body.style.margin = '0';

          return {getContainerElement: () => overlayContainerElement};
        }},
        {provide: Dir, useFactory: () => dir = { value: 'ltr' }},
        {provide: ScrollDispatcher, useFactory: () => {
          return {scrolled: (delay: number, callback: () => any) => {
            return scrolledSubject.asObservable().subscribe(callback);
          }};
        }}
      ]
    });

    TestBed.compileComponents();
  }));

  beforeEach(inject([ViewportRuler], (_ruler: ViewportRuler) => {
    viewportRuler = _ruler;
  }));

  afterEach(() => {
    document.body.removeChild(overlayContainerElement);
  });

  it('should select the proper option when the list of options is initialized at a later point',
    async(() => {
      let fixture = TestBed.createComponent(SelectInitWithoutOptions);
      let instance = fixture.componentInstance;

      fixture.detectChanges();

      // Wait for the initial writeValue promise.
      fixture.whenStable().then(() => {
        expect(instance.select.selected).toBeFalsy();

        instance.addOptions();
        fixture.detectChanges();

        // Wait for the next writeValue promise.
        fixture.whenStable().then(() => {
          expect(instance.select.selected).toBe(instance.options.toArray()[1]);
        });
      });
    }));

  describe('overlay panel', () => {
    let fixture: ComponentFixture<BasicSelect>;
    let trigger: HTMLElement;

    beforeEach(() => {
      fixture = TestBed.createComponent(BasicSelect);
      fixture.detectChanges();

      trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
    });

    it('should open the panel when trigger is clicked', () => {
      trigger.click();
      fixture.detectChanges();

      expect(fixture.componentInstance.select.panelOpen).toBe(true);
      expect(overlayContainerElement.textContent).toContain('Steak');
      expect(overlayContainerElement.textContent).toContain('Pizza');
      expect(overlayContainerElement.textContent).toContain('Tacos');
    });

    it('should close the panel when an item is clicked', async(() => {
      trigger.click();
      fixture.detectChanges();

      const option = overlayContainerElement.querySelector('md-option') as HTMLElement;
      option.click();
      fixture.detectChanges();

      fixture.whenStable().then(() => {
        expect(overlayContainerElement.textContent).toEqual('');
        expect(fixture.componentInstance.select.panelOpen).toBe(false);
      });
    }));

    it('should close the panel when a click occurs outside the panel', async(() => {
      trigger.click();
      fixture.detectChanges();

      const backdrop =
          overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;

      backdrop.click();
      fixture.detectChanges();

      fixture.whenStable().then(() => {
        expect(overlayContainerElement.textContent).toEqual('');
        expect(fixture.componentInstance.select.panelOpen).toBe(false);
      });
    }));

    it('should set the width of the overlay based on the trigger', async(() => {
      trigger.style.width = '200px';

      fixture.whenStable().then(() => {
        trigger.click();
        fixture.detectChanges();
        const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
        expect(pane.style.minWidth).toBe('200px');
      });
    }));

    it('should set the width of the overlay if the element was hidden initially', async(() => {
      let initiallyHidden = TestBed.createComponent(BasicSelectInitiallyHidden);

      initiallyHidden.detectChanges();
      trigger = initiallyHidden.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
      trigger.style.width = '200px';

      initiallyHidden.componentInstance.isVisible = true;
      initiallyHidden.detectChanges();

      initiallyHidden.whenStable().then(() => {
        trigger.click();
        initiallyHidden.detectChanges();

        const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
        expect(pane.style.minWidth).toBe('200px');
      });
    }));

    it('should not attempt to open a select that does not have any options', () => {
      fixture.componentInstance.foods = [];
      fixture.detectChanges();

      trigger.click();
      fixture.detectChanges();

      expect(fixture.componentInstance.select.panelOpen).toBe(false);
    });

    it('should set the width of the overlay if there is no placeholder', async(() => {
      let noPlaceholder = TestBed.createComponent(BasicSelectNoPlaceholder);

      noPlaceholder.detectChanges();
      trigger = noPlaceholder.debugElement.query(By.css('.mat-select-trigger')).nativeElement;

      noPlaceholder.whenStable().then(() => {
        trigger.click();
        noPlaceholder.detectChanges();

        const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
        expect(parseInt(pane.style.minWidth)).toBeGreaterThan(0);
      });
    }));

    it('should close the panel when tabbing out', async(() => {
      trigger.click();
      fixture.detectChanges();
      expect(fixture.componentInstance.select.panelOpen).toBe(true);

      const panel = overlayContainerElement.querySelector('.mat-select-panel');
      dispatchKeyboardEvent(panel, 'keydown', TAB);
      fixture.detectChanges();

      fixture.whenStable().then(() => {
        expect(fixture.componentInstance.select.panelOpen).toBe(false);
      });
    }));

  });

  describe('selection logic', () => {
    let fixture: ComponentFixture<BasicSelect>;
    let trigger: HTMLElement;

    beforeEach(() => {
      fixture = TestBed.createComponent(BasicSelect);
      fixture.detectChanges();

      trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
    });

    it('should display placeholder if no option is selected', () => {
      expect(trigger.textContent.trim()).toEqual('Food');
    });

    it('should focus the first option if no option is selected', async(() => {
      trigger.click();
      fixture.detectChanges();

      fixture.whenStable().then(() => {
        expect(fixture.componentInstance.select._keyManager.activeItemIndex).toEqual(0);
      });
    }));

    it('should select an option when it is clicked', () => {
      trigger.click();
      fixture.detectChanges();

      let option = overlayContainerElement.querySelector('md-option') as HTMLElement;
      option.click();
      fixture.detectChanges();

      trigger.click();
      fixture.detectChanges();

      option = overlayContainerElement.querySelector('md-option') as HTMLElement;

      expect(option.classList).toContain('mat-selected');
      expect(fixture.componentInstance.options.first.selected).toBe(true);
      expect(fixture.componentInstance.select.selected)
        .toBe(fixture.componentInstance.options.first);
    });

    it('should deselect other options when one is selected', () => {
      trigger.click();
      fixture.detectChanges();

      let options =
        overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;

      options[0].click();
      fixture.detectChanges();

      trigger.click();
      fixture.detectChanges();

      options =
        overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      expect(options[1].classList).not.toContain('mat-selected');
      expect(options[2].classList).not.toContain('mat-selected');

      const optionInstances = fixture.componentInstance.options.toArray();
      expect(optionInstances[1].selected).toBe(false);
      expect(optionInstances[2].selected).toBe(false);
    });

    it('should remove selection if option has been removed', async(() => {
      let select = fixture.componentInstance.select;

      trigger.click();
      fixture.detectChanges();

      let firstOption = overlayContainerElement.querySelectorAll('md-option')[0] as HTMLElement;

      firstOption.click();
      fixture.detectChanges();

      expect(select.selected).toBe(select.options.first, 'Expected first option to be selected.');

      fixture.componentInstance.foods = [];
      fixture.detectChanges();

      fixture.whenStable().then(() => {
        expect(select.selected)
          .toBeUndefined('Expected selection to be removed when option no longer exists.');
      });
    }));

    it('should display the selected option in the trigger', () => {
      trigger.click();
      fixture.detectChanges();

      const option = overlayContainerElement.querySelector('md-option') as HTMLElement;
      option.click();
      fixture.detectChanges();

      const value = fixture.debugElement.query(By.css('.mat-select-value')).nativeElement;
      const placeholder =
        fixture.debugElement.query(By.css('.mat-select-placeholder')).nativeElement;

      expect(placeholder.textContent).toContain('Food');
      expect(value.textContent).toContain('Steak');
      expect(trigger.textContent).toContain('Food');
      expect(trigger.textContent).toContain('Steak');
    });

    it('should focus the selected option if an option is selected', async(() => {
      // must wait for initial writeValue promise to finish
      fixture.whenStable().then(() => {
        fixture.componentInstance.control.setValue('pizza-1');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        // must wait for animation to finish
        fixture.whenStable().then(() => {
          fixture.detectChanges();
          expect(fixture.componentInstance.select._keyManager.activeItemIndex).toEqual(1);
        });
      });
    }));

    it('should select an option that was added after initialization', () => {
      fixture.componentInstance.foods.push({viewValue: 'Potatoes', value: 'potatoes-8'});
      trigger.click();
      fixture.detectChanges();

      const options =
        overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      options[8].click();
      fixture.detectChanges();

      expect(trigger.textContent).toContain('Potatoes');
      expect(fixture.componentInstance.select.selected)
        .toBe(fixture.componentInstance.options.last);
    });

    it('should not select disabled options', () => {
      trigger.click();
      fixture.detectChanges();

      const options =
        overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      options[2].click();
      fixture.detectChanges();

      expect(fixture.componentInstance.select.panelOpen).toBe(true);
      expect(options[2].classList).not.toContain('mat-selected');
      expect(fixture.componentInstance.select.selected).not.toBeDefined();
    });

  });

  describe('forms integration', () => {
    let fixture: ComponentFixture<BasicSelect>;
    let trigger: HTMLElement;

    beforeEach(() => {
      fixture = TestBed.createComponent(BasicSelect);
    });

    it('should take an initial view value with reactive forms', () => {
      fixture.componentInstance.control = new FormControl('pizza-1');
      fixture.detectChanges();

      const value = fixture.debugElement.query(By.css('.mat-select-value'));
      expect(value.nativeElement.textContent)
          .toContain('Pizza', `Expected trigger to be populated by the control's initial value.`);

      trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
      trigger.click();
      fixture.detectChanges();

      const options =
          overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      expect(options[1].classList)
          .toContain('mat-selected',
              `Expected option with the control's initial value to be selected.`);
    });

    beforeEach(() => {
      fixture.detectChanges();
      trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
    });

    it('should set the view value from the form', () => {
      let value = fixture.debugElement.query(By.css('.mat-select-value'));
      expect(value).toBeNull('Expected trigger to start with empty value.');

      fixture.componentInstance.control.setValue('pizza-1');
      fixture.detectChanges();

      value = fixture.debugElement.query(By.css('.mat-select-value'));
      expect(value.nativeElement.textContent)
        .toContain('Pizza', `Expected trigger to be populated by the control's new value.`);

      trigger.click();
      fixture.detectChanges();

      const options =
        overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      expect(options[1].classList)
        .toContain('mat-selected', `Expected option with the control's new value to be selected.`);
    });

    it('should update the form value when the view changes', () => {
      expect(fixture.componentInstance.control.value)
        .toEqual(null, `Expected the control's value to be null initially.`);

      trigger.click();
      fixture.detectChanges();

      const option = overlayContainerElement.querySelector('md-option') as HTMLElement;
      option.click();
      fixture.detectChanges();

      expect(fixture.componentInstance.control.value)
        .toEqual('steak-0', `Expected control's value to be set to the new option.`);
    });

    it('should clear the selection when a nonexistent option value is selected', () => {
      fixture.componentInstance.control.setValue('pizza-1');
      fixture.detectChanges();

      fixture.componentInstance.control.setValue('gibberish');
      fixture.detectChanges();

      const value = fixture.debugElement.query(By.css('.mat-select-value'));
      expect(value).toBe(null, `Expected trigger to be cleared when option value is not found.`);
      expect(trigger.textContent)
          .not.toContain('Pizza', `Expected trigger to be cleared when option value is not found.`);

      trigger.click();
      fixture.detectChanges();

      const options =
          overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      expect(options[1].classList)
          .not.toContain('mat-selected', `Expected option with the old value not to be selected.`);
    });

    it('should clear the selection when the control is reset', () => {
      fixture.componentInstance.control.setValue('pizza-1');
      fixture.detectChanges();

      fixture.componentInstance.control.reset();
      fixture.detectChanges();

      const value = fixture.debugElement.query(By.css('.mat-select-value'));
      expect(value).toBe(null, `Expected trigger to be cleared when option value is not found.`);
      expect(trigger.textContent)
          .not.toContain('Pizza', `Expected trigger to be cleared when option value is not found.`);

      trigger.click();
      fixture.detectChanges();

      const options =
          overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      expect(options[1].classList)
          .not.toContain('mat-selected', `Expected option with the old value not to be selected.`);
    });

    it('should set the control to touched when the select is touched', () => {
      expect(fixture.componentInstance.control.touched)
        .toEqual(false, `Expected the control to start off as untouched.`);

      trigger.click();
      dispatchFakeEvent(trigger, 'blur');
      fixture.detectChanges();
      expect(fixture.componentInstance.control.touched)
        .toEqual(false, `Expected the control to stay untouched when menu opened.`);

      const backdrop =
        overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
      backdrop.click();
      dispatchFakeEvent(trigger, 'blur');
      fixture.detectChanges();
      expect(fixture.componentInstance.control.touched)
        .toEqual(true, `Expected the control to be touched as soon as focus left the select.`);
    });

    it('should set the control to dirty when the select\'s value changes in the DOM', () => {
      expect(fixture.componentInstance.control.dirty)
        .toEqual(false, `Expected control to start out pristine.`);

      trigger.click();
      fixture.detectChanges();

      const option = overlayContainerElement.querySelector('md-option') as HTMLElement;
      option.click();
      fixture.detectChanges();

      expect(fixture.componentInstance.control.dirty)
        .toEqual(true, `Expected control to be dirty after value was changed by user.`);
    });

    it('should not set the control to dirty when the value changes programmatically', () => {
      expect(fixture.componentInstance.control.dirty)
        .toEqual(false, `Expected control to start out pristine.`);

      fixture.componentInstance.control.setValue('pizza-1');

      expect(fixture.componentInstance.control.dirty)
        .toEqual(false, `Expected control to stay pristine after programmatic change.`);
    });

    it('should set an asterisk after the placeholder if the control is required', () => {
      const placeholder =
        fixture.debugElement.query(By.css('.mat-select-placeholder')).nativeElement;
      const initialContent =  getComputedStyle(placeholder, '::after').getPropertyValue('content');

      // must support both default cases to work in all browsers in Saucelabs
      expect(initialContent === 'none' || initialContent === '')
        .toBe(true, `Expected placeholder not to have an asterisk, as control was not required.`);

      fixture.componentInstance.isRequired = true;
      fixture.detectChanges();
      expect(getComputedStyle(placeholder, '::after').getPropertyValue('content'))
        .toContain('*', `Expected placeholder to have an asterisk, as control was required.`);
    });

  });

  describe('disabled behavior', () => {

    it('should disable itself when control is disabled programmatically', () => {
      const fixture = TestBed.createComponent(BasicSelect);
      fixture.detectChanges();

      fixture.componentInstance.control.disable();
      fixture.detectChanges();
      let trigger =
        fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
      expect(getComputedStyle(trigger).getPropertyValue('cursor'))
        .toEqual('default', `Expected cursor to be default arrow on disabled control.`);

      trigger.click();
      fixture.detectChanges();

      expect(overlayContainerElement.textContent)
        .toEqual('', `Expected select panel to stay closed.`);
      expect(fixture.componentInstance.select.panelOpen)
        .toBe(false, `Expected select panelOpen property to stay false.`);

      fixture.componentInstance.control.enable();
      fixture.detectChanges();
      expect(getComputedStyle(trigger).getPropertyValue('cursor'))
        .toEqual('pointer', `Expected cursor to be a pointer on enabled control.`);

      trigger.click();
      fixture.detectChanges();

      expect(overlayContainerElement.textContent)
        .toContain('Steak', `Expected select panel to open normally on re-enabled control`);
      expect(fixture.componentInstance.select.panelOpen)
        .toBe(true, `Expected select panelOpen property to become true.`);
    });

    it('should disable itself when control is disabled using the property', async(() => {
      const fixture = TestBed.createComponent(NgModelSelect);
      fixture.detectChanges();

      fixture.componentInstance.isDisabled = true;
      fixture.detectChanges();

      fixture.whenStable().then(() => {
        fixture.detectChanges();
        let trigger =
          fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
        expect(getComputedStyle(trigger).getPropertyValue('cursor'))
          .toEqual('default', `Expected cursor to be default arrow on disabled control.`);

        trigger.click();
        fixture.detectChanges();

        expect(overlayContainerElement.textContent)
          .toEqual('', `Expected select panel to stay closed.`);
        expect(fixture.componentInstance.select.panelOpen)
          .toBe(false, `Expected select panelOpen property to stay false.`);

        fixture.componentInstance.isDisabled = false;
        fixture.detectChanges();

        fixture.whenStable().then(() => {
          fixture.detectChanges();
          expect(getComputedStyle(trigger).getPropertyValue('cursor'))
            .toEqual('pointer', `Expected cursor to be a pointer on enabled control.`);

          trigger.click();
          fixture.detectChanges();

          expect(overlayContainerElement.textContent)
            .toContain('Steak', `Expected select panel to open normally on re-enabled control`);
          expect(fixture.componentInstance.select.panelOpen)
            .toBe(true, `Expected select panelOpen property to become true.`);
        });
      });
    }));

  });

  describe('misc forms', () => {
    it('should support use inside a custom value accessor', () => {
      const fixture = TestBed.createComponent(CompWithCustomSelect);
      spyOn(fixture.componentInstance.customAccessor, 'writeValue');
      fixture.detectChanges();

      expect(fixture.componentInstance.customAccessor.select._control)
          .toBe(null, 'Expected md-select NOT to inherit control from parent value accessor.');
      expect(fixture.componentInstance.customAccessor.writeValue).toHaveBeenCalled();
    });

  });

  describe('animations', () => {
    let fixture: ComponentFixture<BasicSelect>;
    let trigger: HTMLElement;

    beforeEach(fakeAsync(() => {
      fixture = TestBed.createComponent(BasicSelect);
      fixture.detectChanges();

      trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
    }));

    it('should float the placeholder when the panel is open and unselected', () => {
      expect(fixture.componentInstance.select._getPlaceholderAnimationState())
          .toEqual('', 'Expected placeholder to initially have a normal position.');

      trigger.click();
      fixture.detectChanges();
      expect(fixture.componentInstance.select._getPlaceholderAnimationState())
          .toEqual('floating-ltr', 'Expected placeholder to animate up to floating position.');

      const backdrop =
        overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
      backdrop.click();
      fixture.detectChanges();

      expect(fixture.componentInstance.select._getPlaceholderAnimationState())
          .toEqual('', 'Expected placeholder to animate back down to normal position.');
    });

    it('should float the placeholder without animation when value is set', () => {
      fixture.componentInstance.control.setValue('pizza-1');
      fixture.detectChanges();

      const placeholderEl =
          fixture.debugElement.query(By.css('.mat-select-placeholder')).nativeElement;

      expect(placeholderEl.classList)
          .toContain('mat-floating-placeholder', 'Expected placeholder to display as floating.');
      expect(fixture.componentInstance.select._getPlaceholderAnimationState())
          .toEqual('', 'Expected animation state to be empty to avoid animation.');
    });

    it('should use the floating-rtl state when the dir is rtl', () => {
      dir.value = 'rtl';

      trigger.click();
      fixture.detectChanges();
      expect(fixture.componentInstance.select._getPlaceholderAnimationState())
          .toEqual('floating-rtl');
    });

    it('should add a class to the panel when the menu is done animating', fakeAsync(() => {
      trigger.click();
      fixture.detectChanges();

      const panel = overlayContainerElement.querySelector('.mat-select-panel');

      expect(panel.classList).not.toContain('mat-select-panel-done-animating');

      tick(250);
      fixture.detectChanges();

      expect(panel.classList).toContain('mat-select-panel-done-animating');
    }));

  });

  describe('positioning', () => {
    let fixture: ComponentFixture<BasicSelect>;
    let trigger: HTMLElement;
    let select: HTMLElement;

    beforeEach(() => {
      fixture = TestBed.createComponent(BasicSelect);
      fixture.detectChanges();
      trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
      select = fixture.debugElement.query(By.css('md-select')).nativeElement;
    });

    /**
     * Asserts that the given option is aligned with the trigger.
     * @param index The index of the option.
     */
    function checkTriggerAlignedWithOption(index: number): void {
      const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane');
      const triggerTop = trigger.getBoundingClientRect().top;
      const overlayTop = overlayPane.getBoundingClientRect().top;
      const options = overlayPane.querySelectorAll('md-option');
      const optionTop = options[index].getBoundingClientRect().top;

      // The option text should align with the trigger text. Because each option is 18px
      // larger in height than the trigger, the option needs to be adjusted up 9 pixels.
      expect(Math.floor(optionTop))
          .toEqual(Math.floor(triggerTop - 9), `Expected trigger to align with option ${index}.`);

      // For the animation to start at the option's center, its origin must be the distance
      // from the top of the overlay to the option top + half the option height (48/2 = 24).
      const expectedOrigin = Math.floor(optionTop - overlayTop + 24);
      const rawYOrigin = fixture.componentInstance.select._transformOrigin.split(' ')[1].trim();
      const origin = Math.floor(parseInt(rawYOrigin));

      expect(origin).toBe(expectedOrigin,
          `Expected panel animation to originate in the center of option ${index}.`);
    }

    describe('ample space to open', () => {

      beforeEach(() => {
        // these styles are necessary because we are first testing the overlay's position
        // if there is room for it to open to its full extent in either direction.
        select.style.position = 'fixed';
        select.style.top = '300px';
        select.style.left = '20px';
      });

      it('should align the first option with the trigger text if no option is selected', () => {
        trigger.click();
        fixture.detectChanges();

        const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel');

        // The panel should be scrolled to 0 because centering the option is not possible.
        expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to be scrolled.`);

        checkTriggerAlignedWithOption(0);
      });

      it('should align a selected option too high to be centered with the trigger text', () => {
        // Select the second option, because it can't be scrolled any further downward
        fixture.componentInstance.control.setValue('pizza-1');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel');

        // The panel should be scrolled to 0 because centering the option is not possible.
        expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to be scrolled.`);

        checkTriggerAlignedWithOption(1);
      });

      it('should align a selected option in the middle with the trigger text', () => {
        // Select the fifth option, which has enough space to scroll to the center
        fixture.componentInstance.control.setValue('chips-4');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel');

        // The selected option should be scrolled to the center of the panel.
        // This will be its original offset from the scrollTop - half the panel height + half the
        // option height. 4 (index) * 48 (option height) = 192px offset from scrollTop
        // 192 - 256/2 + 48/2 = 88px
        expect(scrollContainer.scrollTop)
            .toEqual(88, `Expected overlay panel to be scrolled to center the selected option.`);

        checkTriggerAlignedWithOption(4);
      });

      it('should align a selected option at the scroll max with the trigger text', () => {
        // Select the last option in the list
        fixture.componentInstance.control.setValue('sushi-7');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel');

        // The selected option should be scrolled to the max scroll position.
        // This will be the height of the scrollContainer - the panel height.
        // 8 options * 48px = 384 scrollContainer height, 384 - 256 = 128px max scroll
        expect(scrollContainer.scrollTop)
            .toEqual(128, `Expected overlay panel to be scrolled to its maximum position.`);

        checkTriggerAlignedWithOption(7);
      });

    });

    describe('limited space to open vertically', () => {

      beforeEach(() => {
        select.style.position = 'fixed';
        select.style.left = '20px';
      });

      it('should adjust position of centered option if there is little space above', () => {
        // Push the select to a position with not quite enough space on the top to open
        // with the option completely centered (needs 113px at least: 256/2 - 48/2 + 9)
        select.style.top = '85px';

        // Select an option in the middle of the list
        fixture.componentInstance.control.setValue('chips-4');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel');

        // Scroll should adjust by the difference between the top space available (85px + 8px
        // viewport padding = 77px) and the height of the panel above the option (113px).
        // 113px - 77px = 36px difference + original scrollTop 88px = 124px
        expect(scrollContainer.scrollTop)
            .toEqual(124, `Expected panel to adjust scroll position to fit in viewport.`);

        checkTriggerAlignedWithOption(4);
      });

      it('should adjust position of centered option if there is little space below', () => {
        // Push the select to a position with not quite enough space on the bottom to open
        // with the option completely centered (needs 113px at least: 256/2 - 48/2 + 9)
        select.style.bottom = '56px';

        // Select an option in the middle of the list
        fixture.componentInstance.control.setValue('chips-4');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel');

        // Scroll should adjust by the difference between the bottom space available
        // (56px from the bottom of the screen - 8px padding = 48px)
        // and the height of the panel below the option (113px).
        // 113px - 48px = 75px difference. Original scrollTop 88px - 75px = 23px
        expect(Math.ceil(scrollContainer.scrollTop))
            .toEqual(23, `Expected panel to adjust scroll position to fit in viewport.`);

        checkTriggerAlignedWithOption(4);
      });

      it('should fall back to "above" positioning if scroll adjustment will not help', () => {
        // Push the select to a position with not enough space on the bottom to open
        select.style.bottom = '56px';
        fixture.detectChanges();

        // Select an option that cannot be scrolled any farther upward
        fixture.componentInstance.control.setValue('coke-0');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        const overlayPane = document.querySelector('.cdk-overlay-pane');
        const triggerBottom = trigger.getBoundingClientRect().bottom;
        const overlayBottom = overlayPane.getBoundingClientRect().bottom;
        const scrollContainer = overlayPane.querySelector('.mat-select-panel');

        // Expect no scroll to be attempted
        expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to be scrolled.`);

        expect(Math.floor(overlayBottom))
            .toEqual(Math.floor(triggerBottom),
                `Expected trigger bottom to align with overlay bottom.`);

        expect(fixture.componentInstance.select._transformOrigin)
            .toContain(`bottom`, `Expected panel animation to originate at the bottom.`);
      });

      it('should fall back to "below" positioning if scroll adjustment will not help', () => {
        // Push the select to a position with not enough space on the top to open
        select.style.top = '85px';

        // Select an option that cannot be scrolled any farther downward
        fixture.componentInstance.control.setValue('sushi-7');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        const overlayPane = document.querySelector('.cdk-overlay-pane');
        const triggerTop = trigger.getBoundingClientRect().top;
        const overlayTop = overlayPane.getBoundingClientRect().top;
        const scrollContainer = overlayPane.querySelector('.mat-select-panel');

        // Expect scroll to remain at the max scroll position
        expect(scrollContainer.scrollTop).toEqual(128, `Expected panel to be at max scroll.`);

        expect(Math.floor(overlayTop))
            .toEqual(Math.floor(triggerTop), `Expected trigger top to align with overlay top.`);

        expect(fixture.componentInstance.select._transformOrigin)
            .toContain(`top`, `Expected panel animation to originate at the top.`);
      });

    });

    describe('limited space to open horizontally', () => {
      beforeEach(() => {
        select.style.position = 'absolute';
        select.style.top = '200px';
      });

      it('should stay within the viewport when overflowing on the left in ltr', fakeAsync(() => {
        select.style.left = '-100px';
        trigger.click();
        tick(400);
        fixture.detectChanges();

        const panelLeft = document.querySelector('.mat-select-panel')
            .getBoundingClientRect().left;
        expect(panelLeft).toBeGreaterThan(0,
            `Expected select panel to be inside the viewport in ltr.`);
      }));

      it('should stay within the viewport when overflowing on the left in rtl', fakeAsync(() => {
        dir.value = 'rtl';
        select.style.left = '-100px';
        trigger.click();
        tick(400);
        fixture.detectChanges();

        const panelLeft = document.querySelector('.mat-select-panel')
            .getBoundingClientRect().left;

        expect(panelLeft).toBeGreaterThan(0,
            `Expected select panel to be inside the viewport in rtl.`);
      }));

      it('should stay within the viewport when overflowing on the right in ltr', fakeAsync(() => {
        select.style.right = '-100px';
        trigger.click();
        tick(400);
        fixture.detectChanges();

        const viewportRect = viewportRuler.getViewportRect().right;
        const panelRight = document.querySelector('.mat-select-panel')
            .getBoundingClientRect().right;

        expect(viewportRect - panelRight).toBeGreaterThan(0,
            `Expected select panel to be inside the viewport in ltr.`);
      }));

      it('should stay within the viewport when overflowing on the right in rtl', fakeAsync(() => {
        dir.value = 'rtl';
        select.style.right = '-100px';
        trigger.click();
        tick(400);
        fixture.detectChanges();

        const viewportRect = viewportRuler.getViewportRect().right;
        const panelRight = document.querySelector('.mat-select-panel')
            .getBoundingClientRect().right;

        expect(viewportRect - panelRight).toBeGreaterThan(0,
            `Expected select panel to be inside the viewport in rtl.`);
      }));

      it('should keep the position within the viewport on repeat openings', async(() => {
        select.style.left = '-100px';
        trigger.click();
        fixture.detectChanges();

        let panelLeft = document.querySelector('.mat-select-panel').getBoundingClientRect().left;

        expect(panelLeft).toBeGreaterThan(0, `Expected select panel to be inside the viewport.`);

        fixture.componentInstance.select.close();
        fixture.detectChanges();

        fixture.whenStable().then(() => {
          trigger.click();
          fixture.detectChanges();
          panelLeft = document.querySelector('.mat-select-panel').getBoundingClientRect().left;

          expect(panelLeft).toBeGreaterThan(0,
              `Expected select panel continue being inside the viewport.`);
        });
      }));

    });

    describe('when scrolled', () => {
      const startingWindowHeight = window.innerHeight;

      // Need to set the scrollTop two different ways to support
      // both Chrome and Firefox.
      function setScrollTop(num: number) {
        document.body.scrollTop = num;
        document.documentElement.scrollTop = num;
      }

      beforeEach(() => {
        // Make the div above the select very tall, so the page will scroll
        fixture.componentInstance.heightAbove = 2000;
        fixture.detectChanges();
        setScrollTop(0);

        // Give the select enough horizontal space to open
        select.style.marginLeft = '20px';
        select.style.marginRight = '20px';
      });

      it('should align the first option properly when scrolled', () => {
        // Give the select enough space to open
        fixture.componentInstance.heightBelow = 400;
        fixture.detectChanges();

        // Scroll the select into view
        setScrollTop(1700);

        trigger.click();
        fixture.detectChanges();

        checkTriggerAlignedWithOption(0);
      });

      it('should align a centered option properly when scrolled', () => {
        // Give the select enough space to open
        fixture.componentInstance.heightBelow = 400;
        fixture.detectChanges();

        fixture.componentInstance.control.setValue('chips-4');
        fixture.detectChanges();

        // Scroll the select into view
        setScrollTop(1700);

        trigger.click();
        fixture.detectChanges();

        checkTriggerAlignedWithOption(4);
      });

      it('should align a centered option properly when scrolling while the panel is open', () => {
        fixture.componentInstance.heightBelow = 400;
        fixture.componentInstance.heightAbove = 400;
        fixture.componentInstance.control.setValue('chips-4');
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        setScrollTop(100);
        scrolledSubject.next();
        fixture.detectChanges();

        checkTriggerAlignedWithOption(4);
      });

      it('should fall back to "above" positioning properly when scrolled', () => {
        // Give the select insufficient space to open below the trigger
        fixture.componentInstance.heightAbove = 0;
        fixture.componentInstance.heightBelow = 100;
        trigger.style.marginTop = '2000px';
        fixture.detectChanges();

        // Scroll the select into view
        setScrollTop(1400);

        // In the iOS simulator (BrowserStack & SauceLabs), adding the content to the
        // body causes karma's iframe for the test to stretch to fit that content once we attempt to
        // scroll the page. Setting width / height / maxWidth / maxHeight on the iframe does not
        // successfully constrain its size. As such, skip assertions in environments where the
        // window size has changed since the start of the test.
        if (window.innerHeight > startingWindowHeight) {
          return;
        }

        trigger.click();
        fixture.detectChanges();

        const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane');
        const triggerBottom = trigger.getBoundingClientRect().bottom;
        const overlayBottom = overlayPane.getBoundingClientRect().bottom;

        expect(Math.floor(overlayBottom))
            .toEqual(Math.floor(triggerBottom),
                `Expected trigger bottom to align with overlay bottom.`);
      });

      it('should fall back to "below" positioning properly when scrolled', () => {
        // Give plenty of space for the select to open below the trigger
        fixture.componentInstance.heightBelow = 650;
        fixture.detectChanges();

        // Select an option too low in the list to fit in limited space above
        fixture.componentInstance.control.setValue('sushi-7');
        fixture.detectChanges();

        // Scroll the select so that it has insufficient space to open above the trigger
        setScrollTop(1950);

        trigger.click();
        fixture.detectChanges();

        const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane');
        const triggerTop = trigger.getBoundingClientRect().top;
        const overlayTop = overlayPane.getBoundingClientRect().top;

        expect(Math.floor(overlayTop))
            .toEqual(Math.floor(triggerTop), `Expected trigger top to align with overlay top.`);
      });

    });

    describe('x-axis positioning', () => {

      beforeEach(() => {
        select.style.position = 'fixed';
        select.style.left = '30px';
      });

      it('should align the trigger and the selected option on the x-axis in ltr', fakeAsync(() => {
        trigger.click();
        tick(400);
        fixture.detectChanges();

        const triggerLeft = trigger.getBoundingClientRect().left;
        const firstOptionLeft = document.querySelector('.cdk-overlay-pane md-option')
            .getBoundingClientRect().left;

        // Each option is 32px wider than the trigger, so it must be adjusted 16px
        // to ensure the text overlaps correctly.
        expect(Math.floor(firstOptionLeft)).toEqual(Math.floor(triggerLeft - 16),
            `Expected trigger to align with the selected option on the x-axis in LTR.`);
      }));

      it('should align the trigger and the selected option on the x-axis in rtl', fakeAsync(() => {
        dir.value = 'rtl';
        fixture.detectChanges();

        trigger.click();
        tick(400);
        fixture.detectChanges();

        const triggerRight = trigger.getBoundingClientRect().right;
        const firstOptionRight =
            document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().right;

        // Each option is 32px wider than the trigger, so it must be adjusted 16px
        // to ensure the text overlaps correctly.
        expect(Math.floor(firstOptionRight))
            .toEqual(Math.floor(triggerRight + 16),
                `Expected trigger to align with the selected option on the x-axis in RTL.`);
      }));
    });

    describe('x-axis positioning in multi select mode', () => {
      let multiFixture: ComponentFixture<MultiSelect>;

      beforeEach(() => {
        multiFixture = TestBed.createComponent(MultiSelect);
        multiFixture.detectChanges();
        trigger = multiFixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
        select = multiFixture.debugElement.query(By.css('md-select')).nativeElement;

        select.style.position = 'fixed';
        select.style.left = '60px';
      });

      it('should adjust for the checkbox in ltr', async(() => {
        trigger.click();
        multiFixture.detectChanges();

        multiFixture.whenStable().then(() => {
          const triggerLeft = trigger.getBoundingClientRect().left;
          const firstOptionLeft =
              document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().left;

          // 48px accounts for the checkbox size, margin and the panel's padding.
          expect(Math.floor(firstOptionLeft))
              .toEqual(Math.floor(triggerLeft - 48),
                  `Expected trigger label to align along x-axis, accounting for the checkbox.`);
        });
      }));

      it('should adjust for the checkbox in rtl', fakeAsync(() => {
        dir.value = 'rtl';
        trigger.click();
        tick(400);
        multiFixture.detectChanges();

        const triggerRight = trigger.getBoundingClientRect().right;
        const firstOptionRight =
            document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().right;

        // 48px accounts for the checkbox size, margin and the panel's padding.
        expect(Math.floor(firstOptionRight))
            .toEqual(Math.floor(triggerRight + 48),
                `Expected trigger label to align along x-axis, accounting for the checkbox.`);
      }));
    });

  });

  describe('accessibility', () => {

    describe('for select', () => {
      let fixture: ComponentFixture<BasicSelect>;
      let select: HTMLElement;

      beforeEach(() => {
        fixture = TestBed.createComponent(BasicSelect);
        fixture.detectChanges();
        select = fixture.debugElement.query(By.css('md-select')).nativeElement;
      });

      it('should set the role of the select to listbox', () => {
        expect(select.getAttribute('role')).toEqual('listbox');
      });

      it('should set the aria label of the select to the placeholder', () => {
        expect(select.getAttribute('aria-label')).toEqual('Food');
      });

      it('should support setting a custom aria-label', () => {
        fixture.componentInstance.ariaLabel = 'Custom Label';
        fixture.detectChanges();

        expect(select.getAttribute('aria-label')).toEqual('Custom Label');
      });

      it('should not set an aria-label if aria-labelledby is specified', () => {
        fixture.componentInstance.ariaLabelledby = 'myLabelId';
        fixture.detectChanges();

        expect(select.getAttribute('aria-label')).toBeFalsy('Expected no aria-label to be set.');
        expect(select.getAttribute('aria-labelledby')).toBe('myLabelId');
      });

      it('should set the tabindex of the select to 0 by default', () => {
        expect(select.getAttribute('tabindex')).toEqual('0');
      });

      it('should be able to override the tabindex', () => {
        fixture.componentInstance.tabIndexOverride = 3;
        fixture.detectChanges();

        expect(select.getAttribute('tabindex')).toBe('3');
      });

      it('should be able to set the tabindex via the native attribute', () => {
        fixture.destroy();

        const plainTabindexFixture = TestBed.createComponent(SelectWithPlainTabindex);

        plainTabindexFixture.detectChanges();
        select = plainTabindexFixture.debugElement.query(By.css('md-select')).nativeElement;

        expect(select.getAttribute('tabindex')).toBe('5');
      });

      it('should set aria-required for required selects', () => {
        expect(select.getAttribute('aria-required'))
          .toEqual('false', `Expected aria-required attr to be false for normal selects.`);

        fixture.componentInstance.isRequired = true;
        fixture.detectChanges();

        expect(select.getAttribute('aria-required'))
          .toEqual('true', `Expected aria-required attr to be true for required selects.`);
      });

      it('should set aria-invalid for selects that are invalid', () => {
        expect(select.getAttribute('aria-invalid'))
          .toEqual('false', `Expected aria-invalid attr to be false for valid selects.`);

        fixture.componentInstance.isRequired = true;
        fixture.detectChanges();

        expect(select.getAttribute('aria-invalid'))
          .toEqual('true', `Expected aria-invalid attr to be true for invalid selects.`);
      });

      it('should set aria-disabled for disabled selects', () => {
        expect(select.getAttribute('aria-disabled')).toEqual('false');

        fixture.componentInstance.control.disable();
        fixture.detectChanges();

        expect(select.getAttribute('aria-disabled')).toEqual('true');
      });

      it('should set the tabindex of the select to -1 if disabled', () => {
        fixture.componentInstance.control.disable();
        fixture.detectChanges();
        expect(select.getAttribute('tabindex')).toEqual('-1');

        fixture.componentInstance.control.enable();
        fixture.detectChanges();
        expect(select.getAttribute('tabindex')).toEqual('0');
      });

      it('should be able to select options via the arrow keys on a closed select', () => {
        const formControl = fixture.componentInstance.control;
        const options = fixture.componentInstance.options.toArray();

        expect(formControl.value).toBeFalsy('Expected no initial value.');

        dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);

        expect(options[0].selected).toBe(true, 'Expected first option to be selected.');
        expect(formControl.value).toBe(options[0].value,
          'Expected value from first option to have been set on the model.');

        dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);
        dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);

        // Note that the third option is skipped, because it is disabled.
        expect(options[3].selected).toBe(true, 'Expected fourth option to be selected.');
        expect(formControl.value).toBe(options[3].value,
          'Expected value from fourth option to have been set on the model.');

        dispatchKeyboardEvent(select, 'keydown', UP_ARROW);

        expect(options[1].selected).toBe(true, 'Expected second option to be selected.');
        expect(formControl.value).toBe(options[1].value,
          'Expected value from second option to have been set on the model.');
      });

      it('should open the panel when pressing the arrow keys on a closed multiple select', () => {
        fixture.destroy();

        const multiFixture = TestBed.createComponent(MultiSelect);
        const instance = multiFixture.componentInstance;

        multiFixture.detectChanges();
        select = multiFixture.debugElement.query(By.css('md-select')).nativeElement;

        const initialValue = instance.control.value;

        expect(instance.select.panelOpen).toBe(false, 'Expected panel to be closed.');

        const event = dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);

        expect(instance.select.panelOpen).toBe(true, 'Expected panel to be open.');
        expect(instance.control.value).toBe(initialValue, 'Expected value to stay the same.');
        expect(event.defaultPrevented).toBe(true, 'Expected default to be prevented.');
      });

      it('should do nothing if the key manager did not change the active item', () => {
        const formControl = fixture.componentInstance.control;

        expect(formControl.value).toBeNull('Expected form control value to be empty.');
        expect(formControl.pristine).toBe(true, 'Expected form control to be clean.');

        dispatchKeyboardEvent(select, 'keydown', 16); // Press a random key.

        expect(formControl.value).toBeNull('Expected form control value to stay empty.');
        expect(formControl.pristine).toBe(true, 'Expected form control to stay clean.');
      });

      it('should continue from the selected option when the value is set programmatically', () => {
        const formControl = fixture.componentInstance.control;

        formControl.setValue('eggs-5');
        fixture.detectChanges();

        dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);

        expect(formControl.value).toBe('pasta-6');
        expect(fixture.componentInstance.options.toArray()[6].selected).toBe(true);
      });

      it('should not cycle through the options if the control is disabled', () => {
        const formControl = fixture.componentInstance.control;

        formControl.setValue('eggs-5');
        formControl.disable();
        dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);

        expect(formControl.value).toBe('eggs-5', 'Expected value to remain unchaged.');
      });

      it('should not wrap selection around after reaching the end of the options', () => {
        const lastOption = fixture.componentInstance.options.last;

        fixture.componentInstance.options.forEach(() => {
          dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);
        });

        expect(lastOption.selected).toBe(true, 'Expected last option to be selected.');

        dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);

        expect(lastOption.selected).toBe(true, 'Expected last option to stay selected.');
      });

      it('should not open a multiple select when tabbing through', () => {
        fixture.destroy();

        const multiFixture = TestBed.createComponent(MultiSelect);

        multiFixture.detectChanges();
        select = multiFixture.debugElement.query(By.css('md-select')).nativeElement;

        expect(multiFixture.componentInstance.select.panelOpen)
            .toBe(false, 'Expected panel to be closed initially.');

        dispatchKeyboardEvent(select, 'keydown', TAB);

        expect(multiFixture.componentInstance.select.panelOpen)
            .toBe(false, 'Expected panel to stay closed.');
      });

      it('should prevent the default action when pressing space', () => {
        let event = dispatchKeyboardEvent(select, 'keydown', SPACE);

        expect(event.defaultPrevented).toBe(true);
      });

    });

    describe('for options', () => {
      let fixture: ComponentFixture<BasicSelect>;
      let trigger: HTMLElement;
      let options: NodeListOf<HTMLElement>;

      beforeEach(() => {
        fixture = TestBed.createComponent(BasicSelect);
        fixture.detectChanges();
        trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
        trigger.click();
        fixture.detectChanges();

        options =
          overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      });

      it('should set the role of md-option to option', () => {
        expect(options[0].getAttribute('role')).toEqual('option');
        expect(options[1].getAttribute('role')).toEqual('option');
        expect(options[2].getAttribute('role')).toEqual('option');
      });

      it('should set aria-selected on each option', () => {
        expect(options[0].getAttribute('aria-selected')).toEqual('false');
        expect(options[1].getAttribute('aria-selected')).toEqual('false');
        expect(options[2].getAttribute('aria-selected')).toEqual('false');

        options[1].click();
        fixture.detectChanges();

        trigger.click();
        fixture.detectChanges();

        expect(options[0].getAttribute('aria-selected')).toEqual('false');
        expect(options[1].getAttribute('aria-selected')).toEqual('true');
        expect(options[2].getAttribute('aria-selected')).toEqual('false');
      });

      it('should set the tabindex of each option according to disabled state', () => {
        expect(options[0].getAttribute('tabindex')).toEqual('0');
        expect(options[1].getAttribute('tabindex')).toEqual('0');
        expect(options[2].getAttribute('tabindex')).toEqual('-1');
      });

      it('should set aria-disabled for disabled options', () => {
        expect(options[0].getAttribute('aria-disabled')).toEqual('false');
        expect(options[1].getAttribute('aria-disabled')).toEqual('false');
        expect(options[2].getAttribute('aria-disabled')).toEqual('true');

        fixture.componentInstance.foods[2]['disabled'] = false;
        fixture.detectChanges();

        expect(options[0].getAttribute('aria-disabled')).toEqual('false');
        expect(options[1].getAttribute('aria-disabled')).toEqual('false');
        expect(options[2].getAttribute('aria-disabled')).toEqual('false');
      });

    });

    describe('aria-owns', () => {
      let fixture: ComponentFixture<ManySelects>;
      let triggers: DebugElement[];
      let options: NodeListOf<HTMLElement>;

      beforeEach(() => {
        fixture = TestBed.createComponent(ManySelects);
        fixture.detectChanges();
        triggers = fixture.debugElement.queryAll(By.css('.mat-select-trigger'));

        triggers[0].nativeElement.click();
        fixture.detectChanges();

        options =
            overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
      });

      it('should set aria-owns properly', async(() => {
        const selects = fixture.debugElement.queryAll(By.css('md-select'));

        expect(selects[0].nativeElement.getAttribute('aria-owns'))
            .toContain(options[0].id, `Expected aria-owns to contain IDs of its child options.`);
        expect(selects[0].nativeElement.getAttribute('aria-owns'))
            .toContain(options[1].id, `Expected aria-owns to contain IDs of its child options.`);

        const backdrop =
            overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
        backdrop.click();
        fixture.detectChanges();

        fixture.whenStable().then(() => {
          triggers[1].nativeElement.click();

          fixture.detectChanges();
          options =
              overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
          expect(selects[1].nativeElement.getAttribute('aria-owns'))
              .toContain(options[0].id, `Expected aria-owns to contain IDs of its child options.`);
          expect(selects[1].nativeElement.getAttribute('aria-owns'))
              .toContain(options[1].id, `Expected aria-owns to contain IDs of its child options.`);
        });

      }));

      it('should set the option id properly', async(() => {
        let firstOptionID = options[0].id;

        expect(options[0].id)
            .toContain('md-option', `Expected option ID to have the correct prefix.`);
        expect(options[0].id).not.toEqual(options[1].id, `Expected option IDs to be unique.`);

        const backdrop =
            overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
        backdrop.click();
        fixture.detectChanges();

        fixture.whenStable().then(() => {
          triggers[1].nativeElement.click();

          fixture.detectChanges();
          options =
              overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
          expect(options[0].id)
              .toContain('md-option', `Expected option ID to have the correct prefix.`);
          expect(options[0].id).not.toEqual(firstOptionID, `Expected option IDs to be unique.`);
          expect(options[0].id).not.toEqual(options[1].id, `Expected option IDs to be unique.`);
        });

      }));

    });

  });

  describe('special cases', () => {

    it('should handle nesting in an ngIf', async(() => {
      const fixture = TestBed.createComponent(NgIfSelect);
      fixture.detectChanges();

      fixture.componentInstance.isShowing = true;
      fixture.detectChanges();

      const trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
      trigger.style.width = '300px';

      fixture.whenStable().then(() => {
        fixture.detectChanges();
        const value = fixture.debugElement.query(By.css('.mat-select-value'));
        expect(value.nativeElement.textContent)
            .toContain('Pizza', `Expected trigger to be populated by the control's initial value.`);

        trigger.click();
        fixture.detectChanges();

        const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
        expect(pane.style.minWidth).toEqual('300px');

        expect(fixture.componentInstance.select.panelOpen).toBe(true);
        expect(overlayContainerElement.textContent).toContain('Steak');
        expect(overlayContainerElement.textContent).toContain('Pizza');
        expect(overlayContainerElement.textContent).toContain('Tacos');
      });
    }));

    it('should not crash the browser when a sibling throws an error on init', async(() => {
      // Note that this test can be considered successful if the error being thrown didn't
      // end up crashing the testing setup altogether.
      expect(() => {
        TestBed.createComponent(SelectWithErrorSibling).detectChanges();
      }).toThrowError(new RegExp('Oh no!', 'g'));
    }));

    it('should not throw when trying to access the selected value on init', async(() => {
      expect(() => {
        TestBed.createComponent(SelectEarlyAccessSibling).detectChanges();
      }).not.toThrow();
    }));

  });

  describe('change event', () => {
    let fixture: ComponentFixture<SelectWithChangeEvent>;
    let trigger: HTMLElement;

    beforeEach(() => {
      fixture = TestBed.createComponent(SelectWithChangeEvent);
      fixture.detectChanges();

      trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
    });

    it('should emit an event when the selected option has changed', () => {
      trigger.click();
      fixture.detectChanges();

      (overlayContainerElement.querySelector('md-option') as HTMLElement).click();

      expect(fixture.componentInstance.changeListener).toHaveBeenCalled();
    });

    it('should not emit multiple change events for the same option', () => {
      trigger.click();
      fixture.detectChanges();

      const option = overlayContainerElement.querySelector('md-option') as HTMLElement;

      option.click();
      option.click();

      expect(fixture.componentInstance.changeListener).toHaveBeenCalledTimes(1);
    });
  });

  describe('floatPlaceholder option', () => {
    let fixture: ComponentFixture<FloatPlaceholderSelect>;

    beforeEach(() => {
      fixture = TestBed.createComponent(FloatPlaceholderSelect);
    });

    it('should be able to disable the floating placeholder', () => {
      let placeholder = fixture.debugElement.query(By.css('.mat-select-placeholder')).nativeElement;

      fixture.componentInstance.floatPlaceholder = 'never';
      fixture.detectChanges();

      expect(placeholder.style.opacity).toBe('1');
      expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBeFalsy();

      fixture.componentInstance.control.setValue('pizza-1');
      fixture.detectChanges();

      expect(placeholder.style.opacity).toBe('0');
      expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBeFalsy();
    });

    it('should be able to always float the placeholder', () => {
      expect(fixture.componentInstance.control.value).toBeFalsy();

      fixture.componentInstance.floatPlaceholder = 'always';
      fixture.detectChanges();

      expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBe('floating-ltr');
    });
  });

  describe('with OnPush change detection', () => {
    it('should set the trigger text based on the value when initialized', async(() => {
      let fixture = TestBed.createComponent(BasicSelectOnPushPreselected);

      fixture.detectChanges();

      fixture.whenStable().then(() => {
        let trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;

        fixture.detectChanges();

        expect(trigger.textContent).toContain('Pizza');
      });
    }));

    it('should update the trigger based on the value', () => {
      let fixture = TestBed.createComponent(BasicSelectOnPush);
      fixture.detectChanges();
      let trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;

      fixture.componentInstance.control.setValue('pizza-1');
      fixture.detectChanges();

      expect(trigger.textContent).toContain('Pizza');

      fixture.componentInstance.control.reset();
      fixture.detectChanges();

      expect(trigger.textContent).not.toContain('Pizza');
    });

  });

  describe('multiple selection', () => {
    let fixture: ComponentFixture<MultiSelect>;
    let testInstance: MultiSelect;
    let trigger: HTMLElement;

    beforeEach(async(() => {
      fixture = TestBed.createComponent(MultiSelect);
      testInstance = fixture.componentInstance;
      fixture.detectChanges();

      trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
    }));

    it('should be able to select multiple values', () => {
      trigger.click();
      fixture.detectChanges();

      const options = overlayContainerElement.querySelectorAll('md-option') as
          NodeListOf<HTMLElement>;

      options[0].click();
      options[2].click();
      options[5].click();
      fixture.detectChanges();

      expect(testInstance.control.value).toEqual(['steak-0', 'tacos-2', 'eggs-5']);
    });

    it('should be able to toggle an option on and off', () => {
      trigger.click();
      fixture.detectChanges();

      const option = overlayContainerElement.querySelector('md-option') as HTMLElement;

      option.click();
      fixture.detectChanges();

      expect(testInstance.control.value).toEqual(['steak-0']);

      option.click();
      fixture.detectChanges();

      expect(testInstance.control.value).toEqual([]);
    });

    it('should update the label', () => {
      trigger.click();
      fixture.detectChanges();

      const options = overlayContainerElement.querySelectorAll('md-option') as
          NodeListOf<HTMLElement>;

      options[0].click();
      options[2].click();
      options[5].click();
      fixture.detectChanges();

      expect(trigger.textContent).toContain('Steak, Tacos, Eggs');

      options[2].click();
      fixture.detectChanges();

      expect(trigger.textContent).toContain('Steak, Eggs');
    });

    it('should be able to set the selected value by taking an array', () => {
      trigger.click();
      testInstance.control.setValue(['steak-0', 'eggs-5']);
      fixture.detectChanges();

      const optionNodes = overlayContainerElement.querySelectorAll('md-option') as
          NodeListOf<HTMLElement>;

      const optionInstances = testInstance.options.toArray();

      expect(optionNodes[0].classList).toContain('mat-selected');
      expect(optionNodes[5].classList).toContain('mat-selected');

      expect(optionInstances[0].selected).toBe(true);
      expect(optionInstances[5].selected).toBe(true);
    });

    it('should override the previously-selected value when setting an array', () => {
      trigger.click();
      fixture.detectChanges();

      const options = overlayContainerElement.querySelectorAll('md-option') as
          NodeListOf<HTMLElement>;

      options[0].click();
      fixture.detectChanges();

      expect(options[0].classList).toContain('mat-selected');

      testInstance.control.setValue(['eggs-5']);
      fixture.detectChanges();

      expect(options[0].classList).not.toContain('mat-selected');
      expect(options[5].classList).toContain('mat-selected');
    });

    it('should not close the panel when clicking on options', () => {
      trigger.click();
      fixture.detectChanges();

      expect(testInstance.select.panelOpen).toBe(true);

      const options = overlayContainerElement.querySelectorAll('md-option') as
          NodeListOf<HTMLElement>;

      options[0].click();
      options[1].click();
      fixture.detectChanges();

      expect(testInstance.select.panelOpen).toBe(true);
    });

    it('should sort the selected options based on their order in the panel', () => {
      trigger.click();
      fixture.detectChanges();

      const options = overlayContainerElement.querySelectorAll('md-option') as
          NodeListOf<HTMLElement>;

      options[2].click();
      options[0].click();
      options[1].click();
      fixture.detectChanges();

      expect(trigger.textContent).toContain('Steak, Pizza, Tacos');
      expect(fixture.componentInstance.control.value).toEqual(['steak-0', 'pizza-1', 'tacos-2']);
    });

    it('should sort the selected options in reverse in rtl', () => {
      dir.value = 'rtl';
      trigger.click();
      fixture.detectChanges();

      const options = overlayContainerElement.querySelectorAll('md-option') as
          NodeListOf<HTMLElement>;

      options[2].click();
      options[0].click();
      options[1].click();
      fixture.detectChanges();

      expect(trigger.textContent).toContain('Tacos, Pizza, Steak');
      expect(fixture.componentInstance.control.value).toEqual(['steak-0', 'pizza-1', 'tacos-2']);
    });

    it('should sort the values, that get set via the model, based on the panel order', () => {
      trigger.click();
      fixture.detectChanges();

      testInstance.control.setValue(['tacos-2', 'steak-0', 'pizza-1']);
      fixture.detectChanges();

      expect(trigger.textContent).toContain('Steak, Pizza, Tacos');
    });

    it('should reverse sort the values, that get set via the model in rtl', () => {
      dir.value = 'rtl';
      trigger.click();
      fixture.detectChanges();

      testInstance.control.setValue(['tacos-2', 'steak-0', 'pizza-1']);
      fixture.detectChanges();

      expect(trigger.textContent).toContain('Tacos, Pizza, Steak');
    });

    it('should throw an exception when trying to set a non-array value', async(() => {
      expect(() => {
        testInstance.control.setValue('not-an-array');
      }).toThrowError(wrappedErrorMessage(new MdSelectNonArrayValueError()));
    }));

    it('should throw an exception when trying to change multiple mode after init', async(() => {
      expect(() => {
        testInstance.select.multiple = false;
      }).toThrowError(wrappedErrorMessage(new MdSelectDynamicMultipleError()));
    }));

    it('should pass the `multiple` value to all of the option instances', async(() => {
      trigger.click();
      fixture.detectChanges();

      fixture.whenStable().then(() => {
        expect(testInstance.options.toArray().every(option => option.multiple)).toBe(true,
            'Expected `multiple` to have been added to initial set of options.');

        testInstance.foods.push({ value: 'cake-8', viewValue: 'Cake' });
        fixture.detectChanges();

        fixture.whenStable().then(() => {
          expect(testInstance.options.toArray().every(option => option.multiple)).toBe(true,
              'Expected `multiple` to have been set on dynamically-added option.');
        });
      });
    }));

  });

  describe('theming', () => {
    let fixture: ComponentFixture<BasicSelectWithTheming>;
    let testInstance: BasicSelectWithTheming;
    let selectElement: HTMLElement;

    beforeEach(async(() => {
      fixture = TestBed.createComponent(BasicSelectWithTheming);
      testInstance = fixture.componentInstance;
      fixture.detectChanges();

      selectElement = fixture.debugElement.query(By.css('.mat-select')).nativeElement;
    }));

    it('should default to the primary theme', () => {
      expect(fixture.componentInstance.select.color).toBe('primary');
      expect(selectElement.classList).toContain('mat-primary');
    });

    it('should be able to override the theme', () => {
      fixture.componentInstance.theme = 'accent';
      fixture.detectChanges();

      expect(fixture.componentInstance.select.color).toBe('accent');
      expect(selectElement.classList).toContain('mat-accent');
      expect(selectElement.classList).not.toContain('mat-primary');
    });

    it('should not be able to set a blank theme', () => {
      fixture.componentInstance.theme = '';
      fixture.detectChanges();

      expect(fixture.componentInstance.select.color).toBe('primary');
      expect(selectElement.classList).toContain('mat-primary');
    });

    it('should pass the theme to the panel', () => {
      fixture.componentInstance.theme = 'warn';
      fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
      fixture.detectChanges();

      const panel = overlayContainerElement.querySelector('.mat-select-panel');

      expect(fixture.componentInstance.select.color).toBe('warn');
      expect(selectElement.classList).toContain('mat-warn');
      expect(panel.classList).toContain('mat-warn');
    });

  });

});

@Component({
  selector: 'basic-select',
  template: `
    <div [style.height.px]="heightAbove"></div>
    <md-select placeholder="Food" [formControl]="control" [required]="isRequired"
      [tabIndex]="tabIndexOverride" [aria-label]="ariaLabel" [aria-labelledby]="ariaLabelledby">
      <md-option *ngFor="let food of foods" [value]="food.value" [disabled]="food.disabled">
        {{ food.viewValue }}
      </md-option>
    </md-select>
    <div [style.height.px]="heightBelow"></div>
  `
})
class BasicSelect {
  foods: any[] = [
    { value: 'steak-0', viewValue: 'Steak' },
    { value: 'pizza-1', viewValue: 'Pizza' },
    { value: 'tacos-2', viewValue: 'Tacos', disabled: true },
    { value: 'sandwich-3', viewValue: 'Sandwich' },
    { value: 'chips-4', viewValue: 'Chips' },
    { value: 'eggs-5', viewValue: 'Eggs' },
    { value: 'pasta-6', viewValue: 'Pasta' },
    { value: 'sushi-7', viewValue: 'Sushi' },
  ];
  control = new FormControl();
  isRequired: boolean;
  heightAbove = 0;
  heightBelow = 0;
  tabIndexOverride: number;
  ariaLabel: string;
  ariaLabelledby: string;

  @ViewChild(MdSelect) select: MdSelect;
  @ViewChildren(MdOption) options: QueryList<MdOption>;
}

@Component({
  selector: 'ng-model-select',
  template: `
    <md-select placeholder="Food" ngModel [disabled]="isDisabled">
      <md-option *ngFor="let food of foods" [value]="food.value">{{ food.viewValue }}</md-option>
    </md-select>
  `
})
class NgModelSelect {
  foods: any[] = [
    { value: 'steak-0', viewValue: 'Steak' },
    { value: 'pizza-1', viewValue: 'Pizza' },
    { value: 'tacos-2', viewValue: 'Tacos' },
  ];
  isDisabled: boolean;

  @ViewChild(MdSelect) select: MdSelect;
  @ViewChildren(MdOption) options: QueryList<MdOption>;
}

@Component({
  selector: 'many-selects',
  template: `
    <md-select placeholder="First">
      <md-option value="one">one</md-option>
      <md-option value="two">two</md-option>
    </md-select>
    <md-select placeholder="Second">
      <md-option value="three">three</md-option>
      <md-option value="four">four</md-option>
    </md-select>
  `
})
class ManySelects {}

@Component({
  selector: 'ng-if-select',
  template: `
    <div *ngIf="isShowing">
      <md-select placeholder="Food I want to eat right now" [formControl]="control">
        <md-option *ngFor="let food of foods" [value]="food.value">
          {{ food.viewValue }}
        </md-option>
      </md-select>
    </div>
  `,
})
class NgIfSelect {
  isShowing = false;
  foods: any[] = [
    { value: 'steak-0', viewValue: 'Steak' },
    { value: 'pizza-1', viewValue: 'Pizza' },
    { value: 'tacos-2', viewValue: 'Tacos'}
  ];
  control = new FormControl('pizza-1');

  @ViewChild(MdSelect) select: MdSelect;
}

@Component({
  selector: 'select-with-change-event',
  template: `
    <md-select (change)="changeListener($event)">
      <md-option *ngFor="let food of foods" [value]="food">{{ food }}</md-option>
    </md-select>
  `
})
class SelectWithChangeEvent {
  foods: string[] = [
    'steak-0',
    'pizza-1',
    'tacos-2',
    'sandwich-3',
    'chips-4',
    'eggs-5',
    'pasta-6',
    'sushi-7'
  ];

  changeListener = jasmine.createSpy('MdSelect change listener');
}

@Component({
  selector: 'select-init-without-options',
  template: `
    <md-select placeholder="Food I want to eat right now" [formControl]="control">
      <md-option *ngFor="let food of foods" [value]="food.value">
        {{ food.viewValue }}
      </md-option>
    </md-select>
  `
})
class SelectInitWithoutOptions {
  foods: any[];
  control = new FormControl('pizza-1');

  @ViewChild(MdSelect) select: MdSelect;
  @ViewChildren(MdOption) options: QueryList<MdOption>;

  addOptions() {
    this.foods = [
      { value: 'steak-0', viewValue: 'Steak' },
      { value: 'pizza-1', viewValue: 'Pizza' },
      { value: 'tacos-2', viewValue: 'Tacos'}
    ];
  }
}

@Component({
  selector: 'custom-select-accessor',
  template: `
    <md-select></md-select>
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: CustomSelectAccessor,
    multi: true
  }]
})
class CustomSelectAccessor implements ControlValueAccessor {
  @ViewChild(MdSelect) select: MdSelect;

  writeValue(val: any): void {}
  registerOnChange(fn: (val: any) => void): void {}
  registerOnTouched(fn: Function): void {}
}

@Component({
  selector: 'comp-with-custom-select',
  template: `
    <custom-select-accessor [formControl]="ctrl">
    </custom-select-accessor>
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: CustomSelectAccessor,
    multi: true
  }]
})
class CompWithCustomSelect {
  ctrl = new FormControl('initial value');
  @ViewChild(CustomSelectAccessor) customAccessor: CustomSelectAccessor;
}

@Component({
  selector: 'select-infinite-loop',
  template: `
    <md-select [(ngModel)]="value"></md-select>
    <throws-error-on-init></throws-error-on-init>
  `
})
class SelectWithErrorSibling {
  value: string;
}

@Component({
  selector: 'throws-error-on-init',
  template: ''
})
export class ThrowsErrorOnInit implements OnInit {
  ngOnInit() {
    throw new Error('Oh no!');
  }
}

@Component({
  selector: 'basic-select-on-push',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <md-select placeholder="Food" [formControl]="control">
      <md-option *ngFor="let food of foods" [value]="food.value">
        {{ food.viewValue }}
      </md-option>
    </md-select>
  `
})
class BasicSelectOnPush {
  foods: any[] = [
    { value: 'steak-0', viewValue: 'Steak' },
    { value: 'pizza-1', viewValue: 'Pizza' },
    { value: 'tacos-2', viewValue: 'Tacos' },
  ];
  control = new FormControl();
}

@Component({
  selector: 'basic-select-on-push-preselected',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <md-select placeholder="Food" [formControl]="control">
      <md-option *ngFor="let food of foods" [value]="food.value">
        {{ food.viewValue }}
      </md-option>
    </md-select>
  `
})
class BasicSelectOnPushPreselected {
  foods: any[] = [
    { value: 'steak-0', viewValue: 'Steak' },
    { value: 'pizza-1', viewValue: 'Pizza' },
    { value: 'tacos-2', viewValue: 'Tacos' },
  ];
  control = new FormControl('pizza-1');
}

@Component({
  selector: 'floating-placeholder-select',
  template: `
    <md-select placeholder="Food I want to eat right now" [formControl]="control"
      [floatPlaceholder]="floatPlaceholder">
        <md-option *ngFor="let food of foods" [value]="food.value">
          {{ food.viewValue }}
        </md-option>
      </md-select>
    `,
})
class FloatPlaceholderSelect {
  floatPlaceholder: MdSelectFloatPlaceholderType = 'auto';
  control = new FormControl();
  foods: any[] = [
    { value: 'steak-0', viewValue: 'Steak' },
    { value: 'pizza-1', viewValue: 'Pizza' },
    { value: 'tacos-2', viewValue: 'Tacos'}
  ];

  @ViewChild(MdSelect) select: MdSelect;
}

@Component({
  selector: 'multi-select',
  template: `
    <md-select multiple placeholder="Food" [formControl]="control">
      <md-option *ngFor="let food of foods" [value]="food.value">{{ food.viewValue }}</md-option>
    </md-select>
  `
})
class MultiSelect {
  foods: any[] = [
    { value: 'steak-0', viewValue: 'Steak' },
    { value: 'pizza-1', viewValue: 'Pizza' },
    { value: 'tacos-2', viewValue: 'Tacos' },
    { value: 'sandwich-3', viewValue: 'Sandwich' },
    { value: 'chips-4', viewValue: 'Chips' },
    { value: 'eggs-5', viewValue: 'Eggs' },
    { value: 'pasta-6', viewValue: 'Pasta' },
    { value: 'sushi-7', viewValue: 'Sushi' },
  ];
  control = new FormControl();

  @ViewChild(MdSelect) select: MdSelect;
  @ViewChildren(MdOption) options: QueryList<MdOption>;
}

@Component({
  selector: 'select-with-plain-tabindex',
  template: `
    <md-select tabindex="5"></md-select>
  `
})
class SelectWithPlainTabindex { }

@Component({
  selector: 'select-early-sibling-access',
  template: `
    <md-select #select="mdSelect"></md-select>
    <div *ngIf="select.selected"></div>
  `
})
class SelectEarlyAccessSibling { }

@Component({
  selector: 'basic-select-initially-hidden',
  template: `
    <md-select [style.display]="isVisible ? 'block' : 'none'">
      <md-option value="value">There are no other options</md-option>
    </md-select>
  `
})
class BasicSelectInitiallyHidden {
  isVisible = false;
}

@Component({
  selector: 'basic-select-no-placeholder',
  template: `
    <md-select>
      <md-option value="value">There are no other options</md-option>
    </md-select>
  `
})
class BasicSelectNoPlaceholder { }

@Component({
  selector: 'basic-select-with-theming',
  template: `
    <md-select placeholder="Food" [color]="theme">
      <md-option value="steak-0">Steak</md-option>
      <md-option value="pizza-1">Pizza</md-option>
    </md-select>
  `
})
class BasicSelectWithTheming {
  @ViewChild(MdSelect) select: MdSelect;
  theme: string;
}

redpajama_set_name: RedPajamaGithub


This sample demonstrates how to use TImage, TBitmap, and other related components to manipulate characteristics of images, including contrast, negatives, and arithmetic operations.
This sample consists of an image editor. You can open up to three images at the same time and perform operations on them: adjust the brightness and contrast, flip the image, apply filters and effects, calculate the sum, difference, and average of two images.
Navigate to Start | Programs | Embarcadero RAD Studio Rio | Samples and open ImageProcessing.cbproj.
The form for the about box to be displayed from the Help menu.
The form displayed when the user chooses to add, subtract, or calculate the average of two images.
The dialog displayed to adjust the brightness and contrast.
Global functions necessary to manipulate images.
The form for the editor itself.
The form displayed when the user chooses to adjust the threshold.
TForm5 represents the class for the about box that is displayed from the Help menu.
TForm3 represents the class for the brightness/contrast form.
TForm1 implements the main form. It handles the visual and nonvisual components and the occurring events.
TForm4 implements the class for the threshold form.
To select a line of pixels in an image, the TBitmap.ScanLine property is used, which returns an array of values. If the image is in grayscale, the array has the same length as the image width. If the image is in color, the array has 3 times the image width, each pixel being represented by three values – blue, green, and red, in that order.
The Sobel and Sobel2 methods implement the sobel operator used in edge detection. In this application, edge detection is used to create the drawing and detailed drawing effects.
addImage, subtractImage, and averageImage apply only to two grayscale images and calculate the sum, difference, or average of pixels in corresponding positions in the two images.
To adjust the brightness and contrast of an image, a linear transformation is applied to every pixel.
The auto-contrast is set by stretching the histogram.
For the old picture effect, every pixel is first converted to its gray correspondent, therefore blue, green, and red will have the same value. Then, a tone of yellow is added by subtracting a constant from the blue value.
This page was last modified on 8 October 2015, at 04:45.


This picture hangs in my local pub. It is a promotional poster for the wine label 'Ladies Who Shoot Their Lunch', great wines from the Strathbogie Ranges. It shows a well dressed lady holding a rifle with her dog, an Irish Setter, heading off to shoot their lunch. It always reminds me of my Mum. We had an Irish Setter that was definitely Mum's dog, but it's not that, rather it is the grit and grace depicted that does it.
Mum was raised in the country, in the post war period, with a bunch of sisters, her father was determined that his daughters could look after themselves. One of my favourite stories from my Mum is when a would-be suitor took her to a rifle range. Mum's first attempt hit the target perfectly in the centre. Her suitor asserted that it couldn't have been her shot. Rather than argue, Mum made a second attempt, this time aiming to graze the previous shot slightly to the side. She was of course successful and simply smiled.
Mum has a state of grace and ease. I've seen my Mum face many stressful situations, I've seen her gather her grit, get on with things, and always with amazing grace. For me grit came easily, grace not so much, more something to aspire to. Recently I've realised that grace may not be as far away as I thought.
Grit is something that I have long understood. Managing stress, not by avoiding it, but by recognising it, sitting comfortably in the uncomfortable, finding the learning, the positive and the way through. Hard work – yes. Rewarding – yes. Exhausting – that too.
It seems obvious now that grace is a way of being, but I'd often tried to practice grace in my reactions to pressure, conflict or stress. Telling myself that I understood all about grit and grace, but now that I understand more, reactive grace doesn't seem wise. I know now that grace is something to be considered, planned and practiced. Proactive grace is my new goal.
My first step is understanding my guiding purpose and goals. Second step is considering where I am spending my time and effort. Third is to consider my health, energy and wellbeing, and that of my loved ones. All of that is fed into making choices about what and when to step up or to ease off, aimed at balancing my grit and determination with a state of grace and ease.
I still want to be like my Mum, but knowing when to call on my grit and when it's time for grace, and working out a natural rhythm is what I aspire to now. Mastering the art of grace just might be the secret to having my cake and eating it too.


text:
Moody Blues Legend Justin Hayward Soars in Solo Tour
November 14, 2014 – 1:12 PM – 1 Comment
By Dr. Nancy Berk Parade @nancyberk
More by Dr. Nancy
Sarah McLachlan Says Her Music Career Has Been 'Such a Gift to Me'
Kree Harrison Says American Idol Taught Her to Never Dilute Her Voice to Prove She Has Something to Say
YouTube Star Jenny Doan Creates Patchwork of Success After Stock Market Crash
(Courtesy of Rogers and Cowan)
What do you wear when you're asked to go backstage and meet a rock legend? I'm not sure, but until last weekend, I'd never had the privilege of worrying about that. Of course, as my luck would have it, this rock star moment happened in mid-life which meant I was bringing a little more baggage to the situation, including a great husband. Still, it was a fabulous dilemma to tackle, and one that disappeared as soon as the music began.
When legendary Moody Blues' guitarist Justin Hayward landed in my city for his solo tour Spirits…Live, I was thrilled. Having chatted earlier with Hayward for Parade.com and my podcast Whine At 9, I knew the energy and passion behind the performance and couldn't wait to watch the magic first hand. I wasn't alone. Hayward packed the house at the historic Carnegie Library Music Hall in Homestead, Pennsylvania. During an evening that included more standing ovations than any concert I have ever attended, Justin Hayward proved why he's been making music and filling venues for decades.
Hayward, who has called his intimate solo tours the "perfect compliment" to the bigger, louder, electric Moody Blues' tour, certainly pegged it accurately. Moodies' fans hung onto every note of the old and new music he so beautifully offered. From the legendary Nights in White Satin to the more recent and equally heartfelt One Day, Someday, Hayward's poetic style and first rate musicianship didn't skip a beat. The artist has always had a way with words and music, but Hayward also truly excels in allowing listeners to connect to his emotional honesty. Watching his solo performance is rather like being in a room with a dear friend. You can't help but listen carefully, think, and enjoy the experience.
Watch a video of Justin Hayward's solo performance One Day, Someday (Spirits Live) here.
And while the crowd was wowed by the star, Justin Hayward's genuine support of younger musicians, including 23-year-old mega-talented guitarist Mike Dawes, touring with him as opener and backup, made them love Hayward more. Legends who are nice always score more points than the divas.
When Hayward left the stage, fans, still on their feet from endless ovations, moved slowly out of the concert hall in that "Why did this have to end?" haze. Fortunately, I didn't have that problem because I still had to meet the man who had wowed us and I was busy worrying about whether my makeup had melted off during the performance in the circa 1898 building. Note: My husband said the hall wasn't hot, but apparently I was. Which I think sounds like the perfect way to meet a rock star.
PS: Justin Hayward was as kind, gracious, and humble as I had suspected– exactly how you hope a legend is "in real life". Now I can cross "back stage with a cool rock star" off my bucket list and, thanks to mid-life, I can also say I was hot when I met a rock star. Who's luckier than me?
Listen to sample tracks from Justin Hayward's Spirits of the Western Sky here.
The Moody Blues' 8-disc set, The Polydor Years (1986-1992) will be released November 25, 2014.
Listen to Nancy's interview with Justin Hayward here, on iTunes, or Stitcher Radio.
Nancy Berk, Ph.D. is a clinical psychologist, author, comic and entertainment analyst. The host of the showbiz podcast Whine At 9, Nancy digs a little deeper as she chats with fascinating celebrities and industry insiders. Her book College Bound and Gagged: How to Help Your Kid Get into a Great College Without Losing Your Savings, Your Relationship, or Your Mind can be seen in the feature film Admission starring Tina Fey and Paul Rudd.
A member has started a discussion.
Click "Read Comment" to view.

redpajama_set_name: RedPajamaCommonCrawl


These scenarios show why it is sometimes in a patient's best interest to go against his or her wishes.
As a doctor in a caring profession, you often must balance the clinical needs of patients with following their wishes. In some cases, you may feel it is common sense to acquiesce to a patient's demands. But sometimes it's in a patient's best interest to go against his or her wishes.
A 54-year-old male presented to Dr. A's office complaining of injuries sustained in an all-terrain vehicle (ATV) accident that occurred two days prior. The man reported he had driven the ATV into a small ditch at about 15 mph and was thrown into soft sand. He said he had rolled as he landed, but he got right up and wrested the damaged ATV out of the ditch and eventually placed it into the bed of his son's pickup truck.
Since the accident, he noticed intermittent, growing soreness at the medial aspect of his right scapula and right triceps. However, he was convinced he simply pulled some muscles. The patient went about his normal activities until he sought care from Dr. A.
Physical assessment of the patient showed a sturdy, middle-aged man of 6'2" and 210 pounds, in good condition, appearing comfortable, and moving with relative ease. Before performing a physical exam, Dr. A requested a limited cervical spine series of X-rays. Initially, the man objected due to a lack of neck symptoms. However, Dr. A persisted and explained the need, and the patient consented to the X-rays.
The three views of his neck clearly revealed very dramatic C6 and C7 fractures with a marked displacement of his C6 vertebral body. The patient was advised of his condition, stabilized, immediately transported to the hospital, and ultimately underwent successful surgery.
A 50-year-old male, who appeared to be fit and in good health, entered Dr. B's practice for the first time. He had received chiropractic treatment elsewhere and had switched to Dr. B's facility for no apparent reason. When the patient was questioned about why he wasn't returning to his previous chiropractor, he became extremely evasive. Dr. B attempted to obtain consent to contact his prior doctor, but the patient refused.
The patient also refused an exam and X-ray and said he just wanted a manipulation. Though Dr. B didn't always require X-rays for new patients, he felt they were necessary here due to the patient's rather unusual history and to evaluate his lumbar pain. When Dr. B insisted upon the x-rays, the patient became belligerent and accused Dr. B of wanting to pad his bill. He then left the office without treatment.
The patient returned one week later with the same complaint. He again demanded treatment without X-rays, which led the doctor to insist upon x-rays even if he had to pay for them himself. This enraged the patient who once again left without treatment.
After three days, the patient returned with complaints of increased low back pain, and he finally allowed Dr. B to perform X-rays. These x-rays revealed a calcified abdominal aneurysm in excess of 5 cm.
An ambulance could transport him to the emergency room immediately. The patient refused both options.
Dr. B drew up a note stating the patient refused to sign any paperwork. Dr. B's office manager, who was present for all conversations with the patient, signed this document.
The following day Dr. B telephoned the patient's home to find out if he received the necessary care. He also sent the patient a registered letter advising him to seek immediate care for his life-threatening situation.
Unfortunately, Dr. B was instructed by a family member never to call again, so what happened with this patient remains a mystery.
Understand first, treat later. In both cases, the patients appeared to be in good physical condition. Some doctors may have wanted to treat the patients to reduce their pain. However, both doctors wisely insisted on X-raying the patients first, due to clinical history and experience.
Obtain permission from patients to contact prior doctors. It is unlikely that a patient would leave a doctor without reason. By contacting prior doctors, you not only obtain the patient's records, you also find out why patients left and whether they had been cooperative. This approach also reflects that you are a patient-centered, conscientious practitioner who will not compromise your clinical judgment.
Be a patient's doctor first. Be aware that patient fear can exhibit itself in many ways, including anger. Though what happened to the "belligerent" patient is unknown, Doctor B may have saved him from immediate harm by not performing the manipulation. In the case of the ATV patient, it is remarkable that he went about his normal activities with virtually no neck pain. Dr. A likely saved this patient from serious injury.
Document a patient's noncompliance. Dr. B made a wise risk management decision to have his staff witness the series of events. As an added precaution, have staff record the event in their own handwriting and initial it.
Follow up. Dr. A ensured the patient's immediate transport to the hospital. Though Dr. B likely knew the patient would not warmly receive his inquiries, he contacted the patient at home and sent him a registered letter advising treatment. An "informed refusal" letter in a patient's file goes a long way in the event of a lawsuit.


Gutters are a critical component for protecting your house from water intrusion. If you notice water poring over the gutters, they are probably clogged with debris….leaves, pine needles, bird nests, etc. Don't like climbing a ladders to clean them out?
Notice that water is leaking from corners or other gutter joints? Are the gutters sagging? These are also signs that your gutters are not doing the job.


text:
Dr. John Huynh
DMD, MS, D.Ortho, FRCD(C)
Dr. John Huynh was born and raised in Calgary and received his Bachelor of Science degree from the University of Calgary. He then received his Doctorate in Dental Medicine (DMD) degree from the Temple University School of Dentistry in Philadelphia, Pennsylvania.
Following dental school, Dr. Huynh continued his education at Saint Louis University's Orthodontic program where he received a Specialty Certificate in Orthodontics and a Master of Science in Dentistry degree. As part of his Master's degree, he conducted research utilizing 3D computer modelling of airways in patients with obstructive sleep apnea. His research was subsequently published a year after he completed his thesis.
In addition, during his residency at Saint Louis University, Dr. Huynh was part of the Cleft lip/palate and Craniofacial team at Cardinal Glennon Hospital where he helped to manage and treat patients with craniofacial deformities.
Dr. Huynh is happily married and has three energetic kids, two boys and a girl. In the spare time that he has, he plays competitive flag football and is an avid sports fan.
© 2022 Mission Orthodontics. All Rights Reserved.