X Tutup
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions karma-js.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module.exports = function(config) {
'node_modules/zone.js/dist/zone.js',
'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',

// Including systemjs because it defines `__eval`, which produces correct stack traces.
'modules/angular2/src/testing/shims_for_IE.js',
Expand Down
29 changes: 29 additions & 0 deletions modules/angular2/src/testing/test_injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export class InjectSetupWrapper {
return new FunctionWithParamTokens(tokens, fn, false, this._providers);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use this opportunity to remove FunctionWithParamTokens from regular paths(and handle it temporarily in till injectAsync is completely removed) since we no longer have a sync and async variations of it.

So the new inject() would just be something like

function inject(tokens, fn): Function {
  return () => {
     var params = tokens.map(t => injector.get(t));
     return FunctionWrapper.apply(fn, params);  
  } 
}

This way inject can be a generic mechanism to execute a function with injected parameters and not return the special FunctionWithParams type.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we necessarily need to get rid of FunctionWithParamTokens - it's useful to store information about what we want to test and it's not tied to Jasmine.

In particular, we need to store information about additional providers somewhere so that we can do:

it('only uses these providers once', withProviders([MyService]).inject([MyService], (service) => {
 ...
});

We could pass the providers around everywhere, but I think it's clearer to store them on an object.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. Agreed.

}

/** @Deprecated {use async(withProviders().inject())} */
injectAsync(tokens: any[], fn: Function): FunctionWithParamTokens {
return new FunctionWithParamTokens(tokens, fn, true, this._providers);
}
Expand All @@ -140,6 +141,8 @@ export function withProviders(providers: () => any) {
}

/**
* @Deprecated {use async(inject())}
*
* Allows injecting dependencies in `beforeEach()` and `it()`. The test must return
* a promise which will resolve when all asynchronous activity is complete.
*
Expand All @@ -161,6 +164,32 @@ export function injectAsync(tokens: any[], fn: Function): FunctionWithParamToken
return new FunctionWithParamTokens(tokens, fn, true);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a doc string for this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

/**
* Wraps a test function in an asynchronous test zone. The test will automatically
* complete when all asynchronous calls within this zone are done. Can be used
* to wrap an {@link inject} call.
*
* Example:
*
* ```
* it('...', async(inject([AClass], (object) => {
* object.doSomething.then(() => {
* expect(...);
* })
* });
* ```
*/
export function async(fn: Function | FunctionWithParamTokens): FunctionWithParamTokens {
if (fn instanceof FunctionWithParamTokens) {
fn.isAsync = true;
return fn;
} else if (fn instanceof Function) {
return new FunctionWithParamTokens([], fn, true);
} else {
throw new BaseException('argument to async must be a function or inject(<Function>)');
}
}

function emptyArray(): Array<any> {
return [];
}
Expand Down
60 changes: 16 additions & 44 deletions modules/angular2/src/testing/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {bind} from 'angular2/core';
import {
FunctionWithParamTokens,
inject,
async,
injectAsync,
TestInjector,
getTestInjector
} from './test_injector';

export {inject, injectAsync} from './test_injector';
export {inject, async, injectAsync} from './test_injector';

export {expect, NgMatchers} from './matchers';

Expand Down Expand Up @@ -122,37 +123,28 @@ export function beforeEachProviders(fn): void {
});
}

function runInAsyncTestZone(fnToExecute, finishCallback: Function, failCallback: Function,
testName = ''): any {
var AsyncTestZoneSpec = Zone['AsyncTestZoneSpec'];
var testZoneSpec = new AsyncTestZoneSpec(finishCallback, failCallback, testName);
var testZone = Zone.current.fork(testZoneSpec);
return testZone.run(fnToExecute);
}

function _isPromiseLike(input): boolean {
return input && !!(input.then);
}

function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | AnyTestFn,
testTimeOut: number): void {
var timeOut = testTimeOut;

if (testFn instanceof FunctionWithParamTokens) {
let testFnT = testFn;
jsmFn(name, (done) => {
var returnedTestValue;
try {
returnedTestValue = testInjector.execute(testFnT);
} catch (err) {
done.fail(err);
return;
}

if (testFnT.isAsync) {
if (_isPromiseLike(returnedTestValue)) {
(<Promise<any>>returnedTestValue).then(() => { done(); }, (err) => { done.fail(err); });
} else {
done.fail('Error: injectAsync was expected to return a promise, but the ' +
' returned value was: ' + returnedTestValue);
}
runInAsyncTestZone(() => testInjector.execute(testFnT), done, done.fail, name);
} else {
if (!(returnedTestValue === undefined)) {
done.fail('Error: inject returned a value. Did you mean to use injectAsync? Returned ' +
'value was: ' + returnedTestValue);
}
testInjector.execute(testFnT);
done();
}
}, timeOut);
Expand All @@ -166,8 +158,6 @@ function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | An
* Wrapper around Jasmine beforeEach function.
*
* beforeEach may be used with the `inject` function to fetch dependencies.
* The test will automatically wait for any asynchronous calls inside the
* injected test function to complete.
*
* See http://jasmine.github.io/ for more details.
*
Expand All @@ -181,26 +171,10 @@ export function beforeEach(fn: FunctionWithParamTokens | AnyTestFn): void {
// }));`
let fnT = fn;
jsmBeforeEach((done) => {

var returnedTestValue;
try {
returnedTestValue = testInjector.execute(fnT);
} catch (err) {
done.fail(err);
return;
}
if (fnT.isAsync) {
if (_isPromiseLike(returnedTestValue)) {
(<Promise<any>>returnedTestValue).then(() => { done(); }, (err) => { done.fail(err); });
} else {
done.fail('Error: injectAsync was expected to return a promise, but the ' +
' returned value was: ' + returnedTestValue);
}
runInAsyncTestZone(() => testInjector.execute(fnT), done, done.fail, 'beforeEach');
} else {
if (!(returnedTestValue === undefined)) {
done.fail('Error: inject returned a value. Did you mean to use injectAsync? Returned ' +
'value was: ' + returnedTestValue);
}
testInjector.execute(fnT);
done();
}
});
Expand All @@ -217,10 +191,8 @@ export function beforeEach(fn: FunctionWithParamTokens | AnyTestFn): void {
/**
* Define a single test case with the given test name and execution function.
*
* The test function can be either a synchronous function, an asynchronous function
* that takes a completion callback, or an injected function created via {@link inject}
* or {@link injectAsync}. The test will automatically wait for any asynchronous calls
* inside the injected test function to complete.
* The test function can be either a synchronous function, the result of {@link async},
* or an injected function created via {@link inject}.
*
* Wrapper around Jasmine it function. See http://jasmine.github.io/ for more details.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
library angular2.test.testing.testing_browser_pec;

/**
* This is intentionally left blank. The public test lib is only for TS/JS
* apps.
*/
main() {}
166 changes: 166 additions & 0 deletions modules/angular2/test/testing/testing_public_browser_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import {
it,
iit,
xit,
describe,
ddescribe,
xdescribe,
expect,
beforeEach,
beforeEachProviders,
inject,
async,
TestComponentBuilder,
fakeAsync,
tick
} from 'angular2/testing';

import {Injectable, bind} from 'angular2/core';
import {Directive, Component, ViewMetadata} from 'angular2/core';
import {PromiseWrapper} from 'angular2/src/facade/promise';
import {XHR} from 'angular2/src/compiler/xhr';
import {XHRImpl} from 'angular2/src/platform/browser/xhr_impl';

// Components for the tests.
class FancyService {
value: string = 'real value';
getAsyncValue() { return Promise.resolve('async value'); }
getTimeoutValue() {
return new Promise((resolve, reject) => { setTimeout(() => {resolve('timeout value')}, 10); })
}
}

@Component({
selector: 'external-template-comp',
templateUrl: '/base/modules/angular2/test/testing/static_assets/test.html'
})
class ExternalTemplateComp {
}

@Component({selector: 'bad-template-comp', templateUrl: 'non-existant.html'})
class BadTemplateUrl {
}

// Tests for angular2/testing bundle specific to the browser environment.
// For general tests, see test/testing/testing_public_spec.ts.
export function main() {
describe('test APIs for the browser', () => {
describe('angular2 jasmine matchers', () => {
describe('toHaveCssClass', () => {
it('should assert that the CSS class is present', () => {
var el = document.createElement('div');
el.classList.add('matias');
expect(el).toHaveCssClass('matias');
});

it('should assert that the CSS class is not present', () => {
var el = document.createElement('div');
el.classList.add('matias');
expect(el).not.toHaveCssClass('fatias');
});
});

describe('toHaveCssStyle', () => {
it('should assert that the CSS style is present', () => {
var 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', () => {
var 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', () => {
var actuallyDone: boolean;

beforeEach(() => { actuallyDone = false; });

afterEach(() => { expect(actuallyDone).toEqual(true); });

it('should run async tests with XHRs', async(() => {
var xhr = new XHRImpl();
xhr.get('/base/modules/angular2/test/testing/static_assets/test.html')
.then(() => { actuallyDone = true; });
}),
10000); // Long timeout here because this test makes an actual XHR.
});

describe('using the test injector with the inject helper', () => {
describe('setting up Providers', () => {
beforeEachProviders(() => [bind(FancyService).toValue(new FancyService())]);

it('provides a real XHR instance',
inject([XHR], (xhr) => { expect(xhr).toBeAnInstanceOf(XHRImpl); }));

it('should allow the use of fakeAsync',
inject([FancyService], fakeAsync((service) => {
var value;
service.getAsyncValue().then(function(val) { value = val; });
tick();
expect(value).toEqual('async value');
})));
});
});

describe('errors', () => {
var originalJasmineIt: any;

var patchJasmineIt = () => {
var deferred = PromiseWrapper.completer();
originalJasmineIt = jasmine.getEnv().it;
jasmine.getEnv().it = (description: string, fn) => {
var done = () => { deferred.resolve() };
(<any>done).fail = (err) => { deferred.reject(err) };
fn(done);
return null;
};
return deferred.promise;
};

var restoreJasmineIt = () => { jasmine.getEnv().it = originalJasmineIt; };

it('should fail when an XHR fails', (done) => {
var itPromise = patchJasmineIt();

it('should fail with an error from a promise',
async(inject([TestComponentBuilder],
(tcb) => { return tcb.createAsync(BadTemplateUrl); })));

itPromise.then(() => { done.fail('Expected test to fail, but it did not'); }, (err) => {
expect(err).toEqual('Uncaught (in promise): Failed to load non-existant.html');
done();
});
restoreJasmineIt();
}, 10000);
});

describe('test component builder', function() {
it('should allow an external templateUrl',
async(inject([TestComponentBuilder],
(tcb: TestComponentBuilder) => {

tcb.createAsync(ExternalTemplateComp)
.then((componentFixture) => {
componentFixture.detectChanges();
expect(componentFixture.debugElement.nativeElement)
.toHaveText('from external template\n');
});
})),
10000); // Long timeout here because this test makes an actual XHR, and is slow on Edge.
});
});
}
Loading
X Tutup