501 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			501 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts
 | 
						|
/* tslint:disable:no-unused-variable */
 | 
						|
import {
 | 
						|
  BadTemplateUrl, ButtonComp,
 | 
						|
  ChildChildComp, ChildComp, ChildWithChildComp,
 | 
						|
  ExternalTemplateComp,
 | 
						|
  FancyService, MockFancyService,
 | 
						|
  InputComp,
 | 
						|
  MyIfComp, MyIfChildComp, MyIfParentComp,
 | 
						|
  MockChildComp, MockChildChildComp,
 | 
						|
  ParentComp,
 | 
						|
  TestProvidersComp, TestViewProvidersComp
 | 
						|
} from './bag';
 | 
						|
 | 
						|
import { DebugElement } from '@angular/core';
 | 
						|
import { By }           from '@angular/platform-browser';
 | 
						|
 | 
						|
import {
 | 
						|
  beforeEach, beforeEachProviders, withProviders,
 | 
						|
  describe, ddescribe, xdescribe,
 | 
						|
  expect, it, iit, xit,
 | 
						|
  async, inject, fakeAsync, tick,
 | 
						|
  ComponentFixture, TestComponentBuilder
 | 
						|
} from '@angular/testing';
 | 
						|
 | 
						|
import { provide }        from '@angular/core';
 | 
						|
import { ViewMetadata }   from '@angular/core';
 | 
						|
 | 
						|
import { Observable }     from 'rxjs/Rx';
 | 
						|
 | 
						|
////////  SPECS  /////////////
 | 
						|
 | 
						|
/// Verify can use Angular testing's DOM abstraction to access DOM
 | 
						|
 | 
						|
describe('angular2 jasmine matchers', () => {
 | 
						|
  describe('toHaveCssClass', () => {
 | 
						|
    it('should assert that the CSS class is present', () => {
 | 
						|
      let el = document.createElement('div');
 | 
						|
      el.classList.add('bombasto');
 | 
						|
      expect(el).toHaveCssClass('bombasto');
 | 
						|
    });
 | 
						|
 | 
						|
    it('should assert that the CSS class is not present', () => {
 | 
						|
      let el = document.createElement('div');
 | 
						|
      el.classList.add('bombasto');
 | 
						|
      expect(el).not.toHaveCssClass('fatias');
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe('toHaveCssStyle', () => {
 | 
						|
    it('should assert that the CSS style is present', () => {
 | 
						|
      let el = document.createElement('div');
 | 
						|
      expect(el).not.toHaveCssStyle('width');
 | 
						|
 | 
						|
      el.style.setProperty('width', '100px');
 | 
						|
      expect(el).toHaveCssStyle('width');
 | 
						|
    });
 | 
						|
 | 
						|
    it('should assert that the styles are matched against the element', () => {
 | 
						|
      let el = document.createElement('div');
 | 
						|
      expect(el).not.toHaveCssStyle({width: '100px', height: '555px'});
 | 
						|
 | 
						|
      el.style.setProperty('width', '100px');
 | 
						|
      expect(el).toHaveCssStyle({width: '100px'});
 | 
						|
      expect(el).not.toHaveCssStyle({width: '100px', height: '555px'});
 | 
						|
 | 
						|
      el.style.setProperty('height', '555px');
 | 
						|
      expect(el).toHaveCssStyle({height: '555px'});
 | 
						|
      expect(el).toHaveCssStyle({width: '100px', height: '555px'});
 | 
						|
    });
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
describe('using the async helper', () => {
 | 
						|
  let actuallyDone = false;
 | 
						|
 | 
						|
  beforeEach(() => { actuallyDone = false; });
 | 
						|
 | 
						|
  afterEach(() => { expect(actuallyDone).toEqual(true); });
 | 
						|
 | 
						|
  it('should run normal test', () => { actuallyDone = true; });
 | 
						|
 | 
						|
  it('should run normal async test', (done: DoneFn) => {
 | 
						|
    setTimeout(() => {
 | 
						|
      actuallyDone = true;
 | 
						|
      done();
 | 
						|
    }, 0);
 | 
						|
  });
 | 
						|
 | 
						|
  it('should run async test with task',
 | 
						|
      async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
 | 
						|
 | 
						|
  it('should run async test with successful promise', async(() => {
 | 
						|
    let p = new Promise(resolve => { setTimeout(resolve, 10); });
 | 
						|
    p.then(() => { actuallyDone = true; });
 | 
						|
  }));
 | 
						|
 | 
						|
  it('should run async test with failed promise', async(() => {
 | 
						|
    let p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
 | 
						|
    p.catch(() => { actuallyDone = true; });
 | 
						|
  }));
 | 
						|
 | 
						|
  it('should run async test with successful Observable', async(() => {
 | 
						|
      let source = Observable.of(true).delay(10);
 | 
						|
      source.subscribe(
 | 
						|
        val => {},
 | 
						|
        err => fail(err),
 | 
						|
        () => {  actuallyDone = true; } // completed
 | 
						|
      );
 | 
						|
    }));
 | 
						|
});
 | 
						|
 | 
						|
describe('using the test injector with the inject helper', () => {
 | 
						|
 | 
						|
  describe('setting up Providers with FancyService', () => {
 | 
						|
    beforeEachProviders(() => [
 | 
						|
      provide(FancyService, {useValue: new FancyService()})
 | 
						|
    ]);
 | 
						|
 | 
						|
    it('should use FancyService',
 | 
						|
      inject([FancyService], (service: FancyService) => {
 | 
						|
        expect(service.value).toEqual('real value');
 | 
						|
    }));
 | 
						|
 | 
						|
    it('test should wait for FancyService.getAsyncValue',
 | 
						|
      async(inject([FancyService], (service: FancyService) => {
 | 
						|
        service.getAsyncValue().then(
 | 
						|
          value => { expect(value).toEqual('async value'); });
 | 
						|
    })));
 | 
						|
 | 
						|
    it('test should wait for FancyService.getTimeoutValue',
 | 
						|
      async(inject([FancyService], (service: FancyService) => {
 | 
						|
        service.getTimeoutValue().then(
 | 
						|
          value => { expect(value).toEqual('timeout value'); });
 | 
						|
    })));
 | 
						|
 | 
						|
    it('test should wait for FancyService.getObservableValue',
 | 
						|
      async(inject([FancyService], (service: FancyService) => {
 | 
						|
        service.getObservableValue().subscribe(
 | 
						|
          value => { expect(value).toEqual('observable value'); }
 | 
						|
        );
 | 
						|
    })));
 | 
						|
 | 
						|
    it('test should wait for FancyService.getObservableDelayValue',
 | 
						|
      async(inject([FancyService], (service: FancyService) => {
 | 
						|
        service.getObservableDelayValue().subscribe(
 | 
						|
          value => { expect(value).toEqual('observable delay value'); }
 | 
						|
        );
 | 
						|
    })));
 | 
						|
 | 
						|
    it('should allow the use of fakeAsync (Experimental)',
 | 
						|
      fakeAsync(inject([FancyService], (service: FancyService) => {
 | 
						|
        let value: any;
 | 
						|
        service.getAsyncValue().then((val: any) => value = val);
 | 
						|
        tick(); // Trigger JS engine cycle until all promises resolve.
 | 
						|
        expect(value).toEqual('async value');
 | 
						|
      })));
 | 
						|
 | 
						|
    describe('using inner beforeEach to inject-and-modify FancyService', () => {
 | 
						|
      beforeEach(inject([FancyService], (service: FancyService) => {
 | 
						|
        service.value = 'value modified in beforeEach';
 | 
						|
      }));
 | 
						|
 | 
						|
      it('should use modified providers',
 | 
						|
        inject([FancyService], (service: FancyService) => {
 | 
						|
          expect(service.value).toEqual('value modified in beforeEach');
 | 
						|
        }));
 | 
						|
    });
 | 
						|
 | 
						|
    describe('using async within beforeEach', () => {
 | 
						|
      beforeEach(async(inject([FancyService], (service: FancyService) => {
 | 
						|
        service.getAsyncValue().then(value => { service.value = value; });
 | 
						|
      })));
 | 
						|
 | 
						|
      it('should use asynchronously modified value ... in synchronous test',
 | 
						|
          inject([FancyService], (service: FancyService) => {
 | 
						|
            expect(service.value).toEqual('async value'); }));
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe('using `withProviders` for per-test provision', () => {
 | 
						|
    it('should inject test-local FancyService for this test',
 | 
						|
      // `withProviders`:  set up providers at individual test level
 | 
						|
      withProviders(() => [provide(FancyService, {useValue: {value: 'fake value'}})])
 | 
						|
 | 
						|
      // now inject and test
 | 
						|
        .inject([FancyService], (service: FancyService) => {
 | 
						|
          expect(service.value).toEqual('fake value');
 | 
						|
        }));
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
describe('test component builder', function() {
 | 
						|
  it('should instantiate a component with valid DOM',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.createAsync(ChildComp).then(fixture => {
 | 
						|
          fixture.detectChanges();
 | 
						|
          expect(fixture.nativeElement).toHaveText('Original Child');
 | 
						|
        });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should allow changing members of the component',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
       tcb.createAsync(MyIfComp).then(fixture => {
 | 
						|
          fixture.detectChanges();
 | 
						|
          expect(fixture.nativeElement).toHaveText('MyIf()');
 | 
						|
 | 
						|
          fixture.debugElement.componentInstance.showMore = true;
 | 
						|
          fixture.detectChanges();
 | 
						|
          expect(fixture.nativeElement).toHaveText('MyIf(More)');
 | 
						|
        });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should support clicking a button',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.createAsync(ButtonComp).then(fixture => {
 | 
						|
 | 
						|
          let comp = <ButtonComp> fixture.componentInstance;
 | 
						|
          expect(comp.wasClicked).toEqual(false, 'wasClicked should be false at start');
 | 
						|
 | 
						|
          let btn = fixture.debugElement.query(By.css('button'));
 | 
						|
          // let btn = fixture.debugElement.query(el => el.name === 'button'); // the hard way
 | 
						|
 | 
						|
          btn.triggerEventHandler('click', null);
 | 
						|
          // btn.nativeElement.click(); // this often works too ... but not all the time!
 | 
						|
          expect(comp.wasClicked).toEqual(true, 'wasClicked should be true after click');
 | 
						|
        });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should support entering text in input box (ngModel)',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
        let origName = 'John';
 | 
						|
        let newName = 'Sally';
 | 
						|
 | 
						|
        tcb.createAsync(InputComp).then(fixture => {
 | 
						|
 | 
						|
          let comp = <InputComp> fixture.componentInstance;
 | 
						|
          expect(comp.name).toEqual(origName, `At start name should be ${origName} `);
 | 
						|
 | 
						|
          let inputBox = <HTMLInputElement> fixture.debugElement.query(By.css('input')).nativeElement;
 | 
						|
          fixture.detectChanges();
 | 
						|
          expect(inputBox.value).toEqual(origName, `At start input box value should be ${origName} `);
 | 
						|
 | 
						|
          inputBox.value = newName;
 | 
						|
          expect(comp.name).toEqual(origName,
 | 
						|
           `Name should still be ${origName} after value change, before detectChanges`);
 | 
						|
 | 
						|
          fixture.detectChanges();
 | 
						|
          expect(inputBox.value).toEqual(newName,
 | 
						|
          `After value change and detectChanges, name should now be ${newName} `);
 | 
						|
        });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should override a template',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.overrideTemplate(MockChildComp, '<span>Mock</span>')
 | 
						|
            .createAsync(MockChildComp)
 | 
						|
            .then(fixture => {
 | 
						|
              fixture.detectChanges();
 | 
						|
              expect(fixture.nativeElement).toHaveText('Mock');
 | 
						|
            });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should override a view',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.overrideView(
 | 
						|
              ChildComp,
 | 
						|
              new ViewMetadata({template: '<span>Modified {{childBinding}}</span>'})
 | 
						|
            )
 | 
						|
            .createAsync(ChildComp)
 | 
						|
            .then(fixture => {
 | 
						|
              fixture.detectChanges();
 | 
						|
              expect(fixture.nativeElement).toHaveText('Modified Child');
 | 
						|
 | 
						|
            });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should override component directives',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.overrideDirective(ParentComp, ChildComp, MockChildComp)
 | 
						|
            .createAsync(ParentComp)
 | 
						|
            .then(fixture => {
 | 
						|
              fixture.detectChanges();
 | 
						|
              expect(fixture.nativeElement).toHaveText('Parent(Mock)');
 | 
						|
 | 
						|
            });
 | 
						|
      })));
 | 
						|
 | 
						|
 | 
						|
  it('should override child component\'s directives',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp)
 | 
						|
            .overrideDirective(ChildWithChildComp, ChildChildComp, MockChildChildComp)
 | 
						|
            .createAsync(ParentComp)
 | 
						|
            .then(fixture => {
 | 
						|
              fixture.detectChanges();
 | 
						|
              expect(fixture.nativeElement)
 | 
						|
                  .toHaveText('Parent(Original Child(ChildChild Mock))');
 | 
						|
 | 
						|
            });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should override a provider',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.overrideProviders(
 | 
						|
              TestProvidersComp,
 | 
						|
              [provide(FancyService, {useClass: MockFancyService})]
 | 
						|
            )
 | 
						|
            .createAsync(TestProvidersComp)
 | 
						|
            .then(fixture => {
 | 
						|
              fixture.detectChanges();
 | 
						|
              expect(fixture.nativeElement)
 | 
						|
                  .toHaveText('injected value: mocked out value');
 | 
						|
            });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should override a viewProvider',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.overrideViewProviders(
 | 
						|
              TestViewProvidersComp,
 | 
						|
              [provide(FancyService, {useClass: MockFancyService})]
 | 
						|
            )
 | 
						|
            .createAsync(TestViewProvidersComp)
 | 
						|
            .then(fixture => {
 | 
						|
              fixture.detectChanges();
 | 
						|
              expect(fixture.nativeElement)
 | 
						|
                  .toHaveText('injected value: mocked out value');
 | 
						|
            });
 | 
						|
      })));
 | 
						|
 | 
						|
  it('should allow an external templateUrl',
 | 
						|
      async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
        tcb.createAsync(ExternalTemplateComp)
 | 
						|
            .then(fixture => {
 | 
						|
              fixture.detectChanges();
 | 
						|
              expect(fixture.nativeElement)
 | 
						|
                  .toHaveText('from external template\n');
 | 
						|
            });
 | 
						|
      })), 10000);  // Long timeout because this test makes an actual XHR.
 | 
						|
 | 
						|
    describe('(lifecycle hooks w/ MyIfParentComp)', () => {
 | 
						|
      let fixture: ComponentFixture;
 | 
						|
      let parent:  MyIfParentComp;
 | 
						|
      let child:   MyIfChildComp;
 | 
						|
 | 
						|
      /**
 | 
						|
       * Get the MyIfChildComp from parent; fail w/ good message if cannot.
 | 
						|
       */
 | 
						|
      function getChild() {
 | 
						|
 | 
						|
        let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp
 | 
						|
 | 
						|
        // The Hard Way: requires detailed knowledge of the parent template
 | 
						|
        try {
 | 
						|
          childDe = fixture.debugElement.children[4].children[0];
 | 
						|
        } catch (err) { /* we'll report the error */ }
 | 
						|
 | 
						|
        // DebugElement.queryAll: if we wanted all of many instances:
 | 
						|
        childDe = fixture.debugElement
 | 
						|
          .queryAll(function (de) { return de.componentInstance instanceof MyIfChildComp; })[0];
 | 
						|
 | 
						|
        // WE'LL USE THIS APPROACH !
 | 
						|
        // DebugElement.query: find first instance (if any)
 | 
						|
        childDe = fixture.debugElement
 | 
						|
          .query(function (de) { return de.componentInstance instanceof MyIfChildComp; });
 | 
						|
 | 
						|
        if (childDe && childDe.componentInstance) {
 | 
						|
          child = childDe.componentInstance;
 | 
						|
        } else {
 | 
						|
          fail('Unable to find MyIfChildComp within MyIfParentComp');
 | 
						|
        }
 | 
						|
 | 
						|
        return child;
 | 
						|
      }
 | 
						|
 | 
						|
      // Create MyIfParentComp TCB and component instance before each test (async beforeEach)
 | 
						|
      beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
         tcb.createAsync(MyIfParentComp)
 | 
						|
            .then(fix => {
 | 
						|
              fixture = fix;
 | 
						|
              parent = fixture.debugElement.componentInstance;
 | 
						|
            });
 | 
						|
      })));
 | 
						|
 | 
						|
      it('should instantiate parent component', () => {
 | 
						|
        expect(parent).not.toBeNull('parent component should exist');
 | 
						|
      });
 | 
						|
 | 
						|
      it('parent component OnInit should NOT be called before first detectChanges()', () => {
 | 
						|
        expect(parent.ngOnInitCalled).toEqual(false);
 | 
						|
      });
 | 
						|
 | 
						|
      it('parent component OnInit should be called after first detectChanges()', () => {
 | 
						|
        fixture.detectChanges();
 | 
						|
        expect(parent.ngOnInitCalled).toEqual(true);
 | 
						|
      });
 | 
						|
 | 
						|
      it('child component should exist after OnInit', () => {
 | 
						|
        fixture.detectChanges();
 | 
						|
        getChild();
 | 
						|
        expect(child instanceof MyIfChildComp).toEqual(true, 'should create child');
 | 
						|
      });
 | 
						|
 | 
						|
      it('should have called child component\'s OnInit ', () => {
 | 
						|
        fixture.detectChanges();
 | 
						|
        getChild();
 | 
						|
        expect(child.ngOnInitCalled).toEqual(true);
 | 
						|
      });
 | 
						|
 | 
						|
      it('child component called OnChanges once', () => {
 | 
						|
        fixture.detectChanges();
 | 
						|
        getChild();
 | 
						|
        expect(child.ngOnChangesCounter).toEqual(1);
 | 
						|
      });
 | 
						|
 | 
						|
      it('changed parent value flows to child', () => {
 | 
						|
        fixture.detectChanges();
 | 
						|
        getChild();
 | 
						|
 | 
						|
        parent.parentValue = 'foo';
 | 
						|
        fixture.detectChanges();
 | 
						|
 | 
						|
        expect(child.ngOnChangesCounter).toEqual(2,
 | 
						|
          'expected 2 changes: initial value and changed value');
 | 
						|
        expect(child.childValue).toEqual('foo',
 | 
						|
          'childValue should eq changed parent value');
 | 
						|
      });
 | 
						|
 | 
						|
      it('changed child value flows to parent', async(() => {
 | 
						|
        fixture.detectChanges();
 | 
						|
        getChild();
 | 
						|
 | 
						|
        child.childValue = 'bar';
 | 
						|
 | 
						|
        return new Promise(resolve => {
 | 
						|
          // Wait one JS engine turn!
 | 
						|
          setTimeout(() => resolve(), 0);
 | 
						|
        }).then(() => {
 | 
						|
          fixture.detectChanges();
 | 
						|
 | 
						|
          expect(child.ngOnChangesCounter).toEqual(2,
 | 
						|
            'expected 2 changes: initial value and changed value');
 | 
						|
          expect(parent.parentValue).toEqual('bar',
 | 
						|
            'parentValue should eq changed parent value');
 | 
						|
        });
 | 
						|
 | 
						|
      }));
 | 
						|
 | 
						|
      it('clicking "Close Child" triggers child OnDestroy', () => {
 | 
						|
        fixture.detectChanges();
 | 
						|
        getChild();
 | 
						|
 | 
						|
        let btn = fixture.debugElement.query(By.css('button'));
 | 
						|
        btn.triggerEventHandler('click', null);
 | 
						|
 | 
						|
        fixture.detectChanges();
 | 
						|
        expect(child.ngOnDestroyCalled).toEqual(true);
 | 
						|
      });
 | 
						|
 | 
						|
    });
 | 
						|
});
 | 
						|
 | 
						|
 | 
						|
//////// Testing Framework Bugs? /////
 | 
						|
import { HeroService }  from './hero.service';
 | 
						|
import { Component }    from '@angular/core';
 | 
						|
 | 
						|
@Component({
 | 
						|
  selector: 'another-comp',
 | 
						|
  template: `AnotherProvidersComp()`,
 | 
						|
  providers: [FancyService] // <======= BOOM! if we comment out
 | 
						|
  // Failed: 'undefined' is not an object (evaluating 'dm.providers.concat')
 | 
						|
})
 | 
						|
export class AnotherProvidersComp {
 | 
						|
  constructor(
 | 
						|
    private _heroService: HeroService
 | 
						|
    ) {  }
 | 
						|
}
 | 
						|
 | 
						|
describe('tcb.overrideProviders', () => {
 | 
						|
  it('Component must have at least one provider else crash',
 | 
						|
    async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
 | 
						|
 | 
						|
    tcb.overrideProviders(
 | 
						|
          AnotherProvidersComp,
 | 
						|
          [provide(HeroService, {useValue: {}})]
 | 
						|
        )
 | 
						|
        .createAsync(AnotherProvidersComp);
 | 
						|
    })));
 | 
						|
});
 |