[jasmine] Improve various library types (#38412)

* Move Func helper to jasmine namespace

* Move ImplementationCallback to jasmine namespace

* Align tests between different versions

* Fix typings for spyOnAllFunctcions

* Improve typing for function matchers

* Refactor AsymmetricMatcher to handle all cases

* Enhance basic expectation docs and typing

Improve toBe(), toEqual(), toMatch(), toHaveClass()
documentation to avoid confusion around them.

* Enhance any() and anything()

* Add missing typing for non-named spy object

* Specify that jasminewd2 depends on jasmine v2

The jasminewd2 library supports jasmine 2 only,
therefore point typings to the correct reference.

* Add NonTypedSpyObj for complex scenarios

* Improve tool for spy names extraction

* Remove undefined Any property from matchers
This commit is contained in:
Alex Povar
2019-09-26 20:58:51 +02:00
committed by Michael Crane
parent 7564c78a0a
commit 7456aa4eec
6 changed files with 662 additions and 226 deletions

View File

@@ -14,11 +14,15 @@
// Moshe Kolodny <https://github.com/kolodny>
// Stephen Farrar <https://github.com/stephenfarrar>
// Mochamad Arfin <https://github.com/ndunks>
// Alex Povar <https://github.com/zvirja>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// For ddescribe / iit use : https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts
type ImplementationCallback = (() => PromiseLike<any>) | (() => void) | ((done: DoneFn) => void);
/**
* @deprecated Use {@link jasmine.ImplementationCallback} instead.
*/
type ImplementationCallback = jasmine.ImplementationCallback;
/**
* Create a group of specs (often called a suite).
@@ -48,7 +52,7 @@ declare function xdescribe(description: string, specDefinitions: () => void): vo
* @param assertion Function that contains the code of your test. If not provided the test will be pending.
* @param timeout Custom timeout for an async spec.
*/
declare function it(expectation: string, assertion?: ImplementationCallback, timeout?: number): void;
declare function it(expectation: string, assertion?: jasmine.ImplementationCallback, timeout?: number): void;
/**
* A focused `it`. If suites or specs are focused, only those that are focused will be executed.
@@ -56,7 +60,7 @@ declare function it(expectation: string, assertion?: ImplementationCallback, tim
* @param assertion Function that contains the code of your test. If not provided the test will be pending.
* @param timeout Custom timeout for an async spec.
*/
declare function fit(expectation: string, assertion?: ImplementationCallback, timeout?: number): void;
declare function fit(expectation: string, assertion?: jasmine.ImplementationCallback, timeout?: number): void;
/**
* A temporarily disabled `it`. The spec will report as pending and will not be executed.
@@ -64,7 +68,7 @@ declare function fit(expectation: string, assertion?: ImplementationCallback, ti
* @param assertion Function that contains the code of your test. If not provided the test will be pending.
* @param timeout Custom timeout for an async spec.
*/
declare function xit(expectation: string, assertion?: ImplementationCallback, timeout?: number): void;
declare function xit(expectation: string, assertion?: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Mark a spec as pending, expectation results will be ignored.
@@ -78,14 +82,14 @@ declare function pending(reason?: string): void;
* @param action Function that contains the code to setup your specs.
* @param timeout Custom timeout for an async beforeEach.
*/
declare function beforeEach(action: ImplementationCallback, timeout?: number): void;
declare function beforeEach(action: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Run some shared teardown after each of the specs in the describe in which it is called.
* @param action Function that contains the code to teardown your specs.
* @param timeout Custom timeout for an async afterEach.
*/
declare function afterEach(action: ImplementationCallback, timeout?: number): void;
declare function afterEach(action: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Run some shared setup once before all of the specs in the describe are run.
@@ -93,7 +97,7 @@ declare function afterEach(action: ImplementationCallback, timeout?: number): vo
* @param action Function that contains the code to setup your specs.
* @param timeout Custom timeout for an async beforeAll.
*/
declare function beforeAll(action: ImplementationCallback, timeout?: number): void;
declare function beforeAll(action: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Run some shared teardown once before all of the specs in the describe are run.
@@ -101,7 +105,7 @@ declare function beforeAll(action: ImplementationCallback, timeout?: number): vo
* @param action Function that contains the code to teardown your specs.
* @param timeout Custom timeout for an async afterAll
*/
declare function afterAll(action: ImplementationCallback, timeout?: number): void;
declare function afterAll(action: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Create an expectation for a spec.
@@ -174,55 +178,69 @@ declare function spyOnProperty<T>(object: T, property: keyof T, accessType?: 'ge
* Installs spies on all writable and configurable properties of an object.
* @param object The object upon which to install the `Spy`s.
*/
declare function spyOnAllFunctions(object: object): jasmine.Spy;
declare function spyOnAllFunctions<T>(object: T): jasmine.SpyObj<T>;
declare function runs(asyncMethod: Function): void;
declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void;
declare function waits(timeout?: number): void;
declare namespace jasmine {
type ExpectedRecursive<T> = T | ObjectContaining<T> | AsymmetricMatcher | {
type Func = (...args: any[]) => any;
// Use trick with prototype to allow abstract classes.
// More info: https://stackoverflow.com/a/38642922/2009373
type Constructor = Function & { prototype: any };
type ImplementationCallback = (() => PromiseLike<any>) | ((done: DoneFn) => void);
type ExpectedRecursive<T> = T | ObjectContaining<T> | AsymmetricMatcher<any> | {
[K in keyof T]: ExpectedRecursive<T[K]> | Any;
};
type Expected<T> = T | ObjectContaining<T> | AsymmetricMatcher | Any | Spy | {
type Expected<T> = T | ObjectContaining<T> | AsymmetricMatcher<any> | Any | Spy | {
[K in keyof T]: ExpectedRecursive<T[K]>;
};
type SpyObjMethodNames<T = undefined> =
T extends undefined ?
(ReadonlyArray<string> | {[methodName: string]: any}) :
(ReadonlyArray<keyof T> | {[P in keyof T]?: ReturnType<T[P] extends (...args: any[]) => any ? T[P] : any>});
(ReadonlyArray<string> | { [methodName: string]: any }) :
(ReadonlyArray<keyof T> | { [P in keyof T]?: T[P] extends Func ? ReturnType<T[P]> : any });
function clock(): Clock;
var matchersUtil: MatchersUtil;
function any(aclass: any): Any;
/**
* That will succeed if the actual value being compared is an instance of the specified class/constructor.
*/
function any(aclass: Constructor | Symbol): AsymmetricMatcher<any>;
function anything(): Any;
/**
* That will succeed if the actual value being compared is not `null` and not `undefined`.
*/
function anything(): AsymmetricMatcher<any>;
/**
* That will succeed if the actual value being compared is `true` or anything truthy.
* @since 3.1.0
*/
function truthy(): Truthy;
function truthy(): AsymmetricMatcher<any>;
/**
* That will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything falsey.
* @since 3.1.0
*/
function falsy(): Falsy;
function falsy(): AsymmetricMatcher<any>;
/**
* That will succeed if the actual value being compared is empty.
* @since 3.1.0
*/
function empty(): Empty;
function empty(): AsymmetricMatcher<any>;
/**
* That will succeed if the actual value being compared is not empty.
* @since 3.1.0
*/
function notEmpty(): NotEmpty;
function notEmpty(): AsymmetricMatcher<any>;
function arrayContaining<T>(sample: ArrayLike<T>): ArrayContaining<T>;
function arrayWithExactContents<T>(sample: ArrayLike<T>): ArrayContaining<T>;
@@ -233,7 +251,7 @@ declare namespace jasmine {
function createSpyObj<T>(baseName: string, methodNames: SpyObjMethodNames<T>): SpyObj<T>;
function createSpyObj(methodNames: SpyObjMethodNames): any;
function createSpyObj<T>(methodNames: SpyObjMethodNames): SpyObj<T>;
function createSpyObj<T>(methodNames: SpyObjMethodNames<T>): SpyObj<T>;
function pp(value: any): string;
@@ -243,11 +261,11 @@ declare namespace jasmine {
function addMatchers(matchers: CustomMatcherFactories): void;
function stringMatching(str: string | RegExp): Any;
function stringMatching(str: string | RegExp): AsymmetricMatcher<string>;
function formatErrorMsg(domain: string, usage: string): (msg: string) => string;
interface Any {
interface Any extends AsymmetricMatcher<any> {
(...params: any[]): any; // jasmine.Any can also be a function
new (expectedClass: any): any;
@@ -255,15 +273,10 @@ declare namespace jasmine {
jasmineToString(): string;
}
interface AsymmetricMatcher<T extends string = string> {
asymmetricMatch(other: any): boolean;
jasmineToString?(): T;
}
interface Truthy extends AsymmetricMatcher<'<jasmine.truthy>'> { }
interface Falsy extends AsymmetricMatcher<'<jasmine.falsy>'> { }
interface Empty extends AsymmetricMatcher<'<jasmine.empty>'> { }
interface NotEmpty extends AsymmetricMatcher<'<jasmine.notEmpty>'> { }
interface AsymmetricMatcher<TValue> {
asymmetricMatch(other: TValue, customTesters: ReadonlyArray<CustomEqualityTester>): boolean;
jasmineToString?(): string;
}
// taken from TypeScript lib.core.es6.d.ts, applicable to CustomMatchers.contains()
interface ArrayLike<T> {
@@ -271,11 +284,11 @@ declare namespace jasmine {
[n: number]: T;
}
interface ArrayContaining<T> extends AsymmetricMatcher {
interface ArrayContaining<T> extends AsymmetricMatcher<any> {
new?(sample: ArrayLike<T>): ArrayLike<T>;
}
interface ObjectContaining<T> {
interface ObjectContaining<T> extends AsymmetricMatcher<any> {
new?(sample: {[K in keyof T]?: any}): {[K in keyof T]?: any};
jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean;
@@ -492,19 +505,33 @@ declare namespace jasmine {
message(): any;
/**
* Expect the actual value to be `===` to the expected value.
*
* @param expected the actual value to be === to the expected value.
* @param expected - The expected value to compare against.
* @param expectationFailOutput
* @example
* expect(thing).toBe(realThing);
*/
toBe(expected: Expected<T>, expectationFailOutput?: any): boolean;
/**
*
* @param expected the actual value to be equal to the expected, using deep equality comparison.
* Expect the actual value to be equal to the expected, using deep equality comparison.
* @param expected - Expected value.
* @param expectationFailOutput
* @example
* expect(bigObject).toEqual({ "foo": ['bar', 'baz'] });
*/
toEqual(expected: Expected<T>, expectationFailOutput?: any): boolean;
/**
* Expect the actual value to match a regular expression.
* @param expected - Value to look for in the string.
* @example
* expect("my string").toMatch(/string$/);
* expect("other string").toMatch("her");
*/
toMatch(expected: string | RegExp, expectationFailOutput?: any): boolean;
toBeDefined(expectationFailOutput?: any): boolean;
toBeUndefined(expectationFailOutput?: any): boolean;
toBeNull(expectationFailOutput?: any): boolean;
@@ -527,7 +554,17 @@ declare namespace jasmine {
toThrowMatching(predicate: (thrown: any) => boolean): boolean;
toBeNegativeInfinity(expectationFailOutput?: any): boolean;
toBePositiveInfinity(expectationFailOutput?: any): boolean;
toHaveClass(expected: any, expectationFailOutput?: any): boolean;
/**
* Expect the actual value to be a DOM element that has the expected class.
* @since 3.0.0
* @param expected - The class name to test for.
* @example
* var el = document.createElement('div');
* el.className = 'foo bar baz';
* expect(el).toHaveClass('bar');
*/
toHaveClass(expected: string, expectationFailOutput?: any): boolean;
/**
* Add some context for an expect.
@@ -535,15 +572,43 @@ declare namespace jasmine {
*/
withContext(message: string): Matchers<T>;
/**
* Invert the matcher following this expect.
*/
not: Matchers<T>;
Any: Any;
}
interface ArrayLikeMatchers<T> extends Matchers<ArrayLike<T>> {
/**
* Expect the actual value to be `===` to the expected value.
*
* @param expected - The expected value to compare against.
* @param expectationFailOutput
* @example
* expect(thing).toBe(realThing);
*/
toBe(expected: Expected<ArrayLike<T>> | ArrayContaining<T>, expectationFailOutput?: any): boolean;
/**
* Expect the actual value to be equal to the expected, using deep equality comparison.
* @param expected - Expected value.
* @param expectationFailOutput
* @example
* expect(bigObject).toEqual({ "foo": ['bar', 'baz'] });
*/
toEqual(expected: Expected<ArrayLike<T>> | ArrayContaining<T>, expectationFailOutput?: any): boolean;
toContain(expected: Expected<T>, expectationFailOutput?: any): boolean;
/**
* Add some context for an expect.
* @param message - Additional context to show when the matcher fails.
*/
withContext(message: string): ArrayLikeMatchers<T>;
/**
* Invert the matcher following this expect.
*/
not: ArrayLikeMatchers<T>;
}
@@ -745,7 +810,7 @@ declare namespace jasmine {
}
type SpyObj<T> = T & {
[k in keyof T]: T[k] extends Function ? T[k] & Spy : T[k];
[K in keyof T]: T[K] extends Function ? T[K] & Spy : T[K];
};
interface SpyAnd {

View File

@@ -15,23 +15,29 @@ describe("A suite is just a function", () => {
});
});
describe("The 'toBe' matcher compares with ===", () => {
it("and has a positive case", () => {
expect(true).toBe(true);
});
it("and can have a negative case", () => {
expect(false).not.toBe(true);
});
});
describe("Included matchers:", () => {
it("The 'toBe' matcher compares with ===", () => {
const a = 12;
const b = a;
describe('toBe', () => {
it("and has a positive case", () => {
expect(true).toBe(true);
});
expect(a).toBe(b);
expect(a).not.toBe(24);
it("and can have a negative case", () => {
expect(false).not.toBe(true);
});
it("the 'toBe' matcher compares with ===", () => {
const a = 12;
const b = a;
expect(a).toBe(b);
expect(a).not.toBe(24);
});
it('should allow to accept any union type', () => {
const value: number | string = null as any;
expect(value).toBe(12);
});
});
describe("The 'toEqual' matcher", () => {
@@ -176,8 +182,10 @@ describe("Included matchers:", () => {
it("async matchers", async () => {
const badness = new Error("badness");
await expectAsync(Promise.resolve()).toBeResolved();
await expectAsync(Promise.resolve()).toBeResolved("good job");
await expectAsync(Promise.resolve(true)).toBeResolvedTo(true);
await expectAsync(Promise.reject(badness)).toBeRejected();
await expectAsync(Promise.reject(badness)).toBeRejected("bad mojo");
await expectAsync(Promise.reject(badness)).toBeRejectedWith(badness);
await expectAsync(Promise.resolve()).withContext("additional info").toBeResolved();
});
@@ -209,7 +217,9 @@ describe("toBePositiveInfinity", () => {
});
describe("toHaveClass", () => {
expect("").toHaveClass(Array);
const element: HTMLElement = null!;
expect(element).toHaveClass("some-class");
expect(element).toHaveClass(Element); // $ExpectError
});
describe("A spec", () => {
@@ -396,22 +406,19 @@ describe("A spy, when configured to call through", () => {
});
describe("A spy, when configured to fake a return value", () => {
var foo: any, bar: any, fetchedBar: any;
beforeEach(() => {
foo = {
setBar: (value: any) => {
bar = value;
},
getBar: () => {
return bar;
}
};
var bar: number;
const foo = {
setBar: (value: number) => {
bar = value;
},
getBar: () => {
return bar;
}
};
it("verifies return value type", () => {
spyOn(foo, "getBar").and.returnValue(745);
foo.setBar(123);
fetchedBar = foo.getBar();
spyOn(foo, "getBar").and.returnValue("42"); // Is an error with TS 3.1+ typings.
});
it("tracks that the spy was called", () => {
@@ -423,6 +430,8 @@ describe("A spy, when configured to fake a return value", () => {
});
it("when called returns the requested value", () => {
const fetchedBar = foo.getBar();
expect(fetchedBar).toEqual(745);
});
});
@@ -720,6 +729,29 @@ describe("A spy, when created manually", () => {
});
});
describe("Spy for generic method", () => {
interface Test {
method<T>(): Array<Box<T>>;
}
interface Box<T> {
value: T;
}
it("should allow to configure generic method", () => {
const spy = jasmine.createSpyObj<Test>('test', ['method']);
spy.method.and.returnValue([{ value: 1 }, { value: 2 }]);
});
it("should allow to configure generic method with non-named spy", () => {
const spy = jasmine.createSpyObj<Test>(['method']);
jasmine.createSpyObj<Test>(['methodUnknown']); // $ExpectError
spy.method.and.returnValue([{ value: 1 }, { value: 2 }]);
});
});
describe("Multiple spies, when created manually", () => {
class Tape {
private rewindTo: number;
@@ -770,6 +802,12 @@ describe("Multiple spies, when created manually", () => {
it("tracks all the arguments of its calls", () => {
expect(tape.rewind).toHaveBeenCalledWith(0);
expect(tape.rewind).toHaveBeenCalledWith('42'); // Is an error with TS 3.1+ typings.
expect(tape.rewind).toHaveBeenCalledWith(jasmine.anything());
expect(tape.rewind).toHaveBeenCalledWith(jasmine.falsy());
expect(tape.rewind).not.toHaveBeenCalledWith(1);
expect(tape.rewind).not.toHaveBeenCalledWith('42'); // Is an error with TS 3.1+ typings.
expect(tape.rewind).not.toHaveBeenCalledWith(jasmine.truthy());
});
it("read isPlaying property", () => {
@@ -784,13 +822,19 @@ describe("multiple spies, when created with spyOnAllFunctions", () => {
y: (a: number) => a,
};
spyOnAllFunctions(obj);
const spy = spyOnAllFunctions(obj);
spy.x.and.returnValue(42);
spy.y.and.returnValue(24);
spy.z; // $ExpectError
obj.x(0);
obj.y(1);
expect(obj.x).toHaveBeenCalled();
expect(obj.y).toHaveBeenCalledWith(1);
expect(spy.y).toHaveBeenCalledWith(1);
expect(spy.y).toHaveBeenCalledWith("one"); // Is an error with TS 3.1+ typings.
});
});
@@ -804,6 +848,9 @@ describe("jasmine.any", () => {
it("matches any value", () => {
expect({}).toEqual(jasmine.any(Object));
expect(12).toEqual(jasmine.any(Number));
expect(42).toEqual(jasmine.any(42)); // $ExpectError
expect({}).toEqual(jasmine.any({})); // $ExpectError
expect(() => null).toEqual(jasmine.any(Function));
});
it("matches any function", () => {
@@ -817,7 +864,7 @@ describe("jasmine.any", () => {
fn2: (param1: number) => param1,
};
const expected: Test = {
const expected = {
fn1: jasmine.any(Function),
fn2: jasmine.any(Function),
};
@@ -825,6 +872,29 @@ describe("jasmine.any", () => {
expect(a).toEqual(expected);
});
it("matches custom types", () => {
class Test { }
const obj = new Test();
expect(obj).toEqual(jasmine.any(Test));
});
it("matches base abstract class", () => {
abstract class TestClassBase { }
class TestClass extends TestClassBase { }
const obj = new TestClass();
expect(obj).toEqual(jasmine.any(TestClass));
expect(obj).toEqual(jasmine.any(TestClassBase));
});
it("matches symbols", () => {
const sym = Symbol('test symbol');
expect(sym).toEqual(jasmine.any(sym));
});
describe("when used with a spy", () => {
it("is useful for comparing arguments", () => {
const foo = jasmine.createSpy('foo');
@@ -834,6 +904,15 @@ describe("jasmine.any", () => {
expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
});
it("is useful for comparing arguments for typed spy", () => {
const foo = jasmine.createSpy('foo');
foo(12, () => {
return true;
});
expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
});
});
});
@@ -949,6 +1028,15 @@ describe("jasmine.arrayContaining", () => {
expect(foo).not.toBe(jasmine.arrayContaining([6]));
});
it("matches read-only array", () => {
const bar: ReadonlyArray<number> = [1, 2, 3, 4];
expect(bar).toEqual(jasmine.arrayContaining([3, 1]));
expect(bar).not.toEqual(jasmine.arrayContaining([6]));
expect(bar).toBe(jasmine.arrayContaining([3, 1]));
});
describe("when used with a spy", () => {
it("is useful when comparing arguments", () => {
const callback = jasmine.createSpy('callback');
@@ -1206,6 +1294,75 @@ describe("Custom matcher: 'toBeGoofy'", () => {
});
});
describe('better typed spys', () => {
describe('a typed spy', () => {
const spy = jasmine.createSpy('spy', (num: number, str: string) => {
return `${num} and ${str}`;
});
it('has a typed returnValue', () => {
// $ExpectType (val: any) => Spy
spy.and.returnValue;
});
it('has a typed calls property', () => {
spy.calls.first().args; // $ExpectType any[]
spy.calls.first().returnValue; // $ExpectType any
});
it('has a typed callFake', () => {
// $ExpectType (fn: Function) => Spy
spy.and.callFake;
});
});
describe('spyOn', () => {
it('only works on methods', () => {
const foo = {
method() {
return 'baz';
},
value: 'value',
};
const spy = spyOn(foo, 'method');
const spy2 = spyOn(foo, 'value'); // Is an error with TS 3.1+ typings.
// $ExpectType any
spy.calls.first().returnValue;
});
it('works on constructors', () => {
class MyClass {
constructor(readonly foo: string) {}
}
const namespace = { MyClass };
const spy = spyOn(namespace, 'MyClass');
spy.and.returnValue({foo: 'test'});
spy.and.returnValue({}); // Is an error with TS 3.1+ typings.
spy.and.returnValue({foo: 123}); // Is an error with TS 3.1+ typings.
});
it('can allows overriding the generic', () => {
class Base {
service() {}
}
class Super extends Base {
service2() {}
}
spyOn<Base>(new Super(), 'service');
spyOn<Base>(new Super(), 'service2'); // $ExpectError
});
});
describe('createSpyObj', () => {
it('returns the correct spy types', () => {
const foo = {
method() {
return 'baz';
},
value: 'value',
};
const spyObj = jasmine.createSpyObj<typeof foo>('foo', ['method']);
// $ExpectType (val: any) => Spy
spyObj.method.and.returnValue;
});
});
});
// test based on http://jasmine.github.io/2.5/custom_reporter.html
var myReporter: jasmine.CustomReporter = {
jasmineStarted: (suiteInfo: jasmine.SuiteInfo) => {
@@ -1318,46 +1475,73 @@ describe("createSpyObj", function() {
});
describe('Static Matcher Test', function() {
it('Falsy', () => {
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: jasmine.falsy(),
})
);
});
it('Truthy', () => {
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: jasmine.truthy(),
})
);
});
it('Empty', () => {
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: jasmine.empty(),
})
);
});
it('NotEmpty', () => {
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: jasmine.notEmpty(),
})
);
});
it('Partial should OK', () => {
expect({ value: null, label: 'abcd' }).toEqual(
jasmine.objectContaining({
value: jasmine.anything(),
})
);
expect({ value: null }).toEqual(
it('Falsy', () => {
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: 'any value should ok',
value: jasmine.falsy(),
})
);
});
});
it('Truthy', () => {
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: jasmine.truthy(),
})
);
});
it('Empty', () => {
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: jasmine.empty(),
})
);
});
it('NotEmpty', () => {
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: jasmine.notEmpty(),
})
);
});
it('Partial should OK', () => {
expect({ value: null, label: 'abcd' }).toEqual(
jasmine.objectContaining({
value: jasmine.anything(),
})
);
expect({ value: null }).toEqual(
jasmine.objectContaining({
value: 'any value should ok',
})
);
});
});
describe("User scenarios", () => {
describe("https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34080", () => {
interface Test {
f(): string;
f(x: any): number;
}
it("has a way to opt out of inferred function types", () => {
const spyObject: jasmine.SpyObj<Test> = jasmine.createSpyObj<Test>("spyObject", ["f"]);
spyObject.f.and.returnValue("a string - working");
const spy2 = jasmine.createSpyObj<Test>(['f']);
spy2.f.and.returnValue("can return string" as any);
});
it("should be possible to opt out for spyOn", () => {
const obj: Test = null!;
const spy1: jasmine.Spy = spyOn(obj, "f");
spy1.and.returnValue("can return string");
const spy2 = spyOn(obj, "f");
spy2.and.returnValue("can return string" as any);
});
});
});
(() => {

View File

@@ -12,10 +12,13 @@
// Moshe Kolodny <https://github.com/kolodny>
// Stephen Farrar <https://github.com/stephenfarrar>
// Mochamad Arfin <https://github.com/ndunks>
// Alex Povar <https://github.com/zvirja>
// For ddescribe / iit use : https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts
type ImplementationCallback = (() => Promise<any>) | ((done: DoneFn) => void);
type InferableFunction = (...args: any[]) => any;
/**
* @deprecated Use {@link jasmine.ImplementationCallback} instead.
*/
type ImplementationCallback = jasmine.ImplementationCallback;
/**
* Create a group of specs (often called a suite).
@@ -45,7 +48,7 @@ declare function xdescribe(description: string, specDefinitions: () => void): vo
* @param assertion Function that contains the code of your test. If not provided the test will be pending.
* @param timeout Custom timeout for an async spec.
*/
declare function it(expectation: string, assertion?: ImplementationCallback, timeout?: number): void;
declare function it(expectation: string, assertion?: jasmine.ImplementationCallback, timeout?: number): void;
/**
* A focused `it`. If suites or specs are focused, only those that are focused will be executed.
@@ -53,7 +56,7 @@ declare function it(expectation: string, assertion?: ImplementationCallback, tim
* @param assertion Function that contains the code of your test. If not provided the test will be pending.
* @param timeout Custom timeout for an async spec.
*/
declare function fit(expectation: string, assertion?: ImplementationCallback, timeout?: number): void;
declare function fit(expectation: string, assertion?: jasmine.ImplementationCallback, timeout?: number): void;
/**
* A temporarily disabled `it`. The spec will report as pending and will not be executed.
@@ -61,7 +64,7 @@ declare function fit(expectation: string, assertion?: ImplementationCallback, ti
* @param assertion Function that contains the code of your test. If not provided the test will be pending.
* @param timeout Custom timeout for an async spec.
*/
declare function xit(expectation: string, assertion?: ImplementationCallback, timeout?: number): void;
declare function xit(expectation: string, assertion?: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Mark a spec as pending, expectation results will be ignored.
@@ -75,14 +78,14 @@ declare function pending(reason?: string): void;
* @param action Function that contains the code to setup your specs.
* @param timeout Custom timeout for an async beforeEach.
*/
declare function beforeEach(action: ImplementationCallback, timeout?: number): void;
declare function beforeEach(action: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Run some shared teardown after each of the specs in the describe in which it is called.
* @param action Function that contains the code to teardown your specs.
* @param timeout Custom timeout for an async afterEach.
*/
declare function afterEach(action: ImplementationCallback, timeout?: number): void;
declare function afterEach(action: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Run some shared setup once before all of the specs in the describe are run.
@@ -90,7 +93,7 @@ declare function afterEach(action: ImplementationCallback, timeout?: number): vo
* @param action Function that contains the code to setup your specs.
* @param timeout Custom timeout for an async beforeAll.
*/
declare function beforeAll(action: ImplementationCallback, timeout?: number): void;
declare function beforeAll(action: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Run some shared teardown once before all of the specs in the describe are run.
@@ -98,14 +101,14 @@ declare function beforeAll(action: ImplementationCallback, timeout?: number): vo
* @param action Function that contains the code to teardown your specs.
* @param timeout Custom timeout for an async afterAll
*/
declare function afterAll(action: ImplementationCallback, timeout?: number): void;
declare function afterAll(action: jasmine.ImplementationCallback, timeout?: number): void;
/**
* Create an expectation for a spec.
* @checkReturnValue see https://tsetse.info/check-return-value
* @param spy
*/
declare function expect(spy: Function): jasmine.Matchers<any>;
declare function expect<T extends jasmine.Func>(spy: T | jasmine.Spy<T>): jasmine.FunctionMatchers<T>;
/**
* Create an expectation for a spec.
@@ -160,9 +163,9 @@ interface DoneFn extends Function {
declare function spyOn<T, K extends keyof T = keyof T>(
object: T, method: T[K] extends Function ? K : never,
): jasmine.Spy<
T[K] extends InferableFunction ? T[K] :
T[K] extends {new (...args: infer A): infer V} ? (...args: A) => V :
T[K] extends Function ? InferableFunction : never
T[K] extends jasmine.Func ? T[K] :
T[K] extends { new (...args: infer A): infer V } ? (...args: A) => V :
never
>;
/**
@@ -177,72 +180,80 @@ declare function spyOnProperty<T>(object: T, property: keyof T, accessType?: 'ge
* Installs spies on all writable and configurable properties of an object.
* @param object The object upon which to install the `Spy`s.
*/
declare function spyOnAllFunctions(object: object): jasmine.Spy;
declare function spyOnAllFunctions<T>(object: T): jasmine.SpyObj<T>;
declare function runs(asyncMethod: Function): void;
declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void;
declare function waits(timeout?: number): void;
declare namespace jasmine {
type ExpectedRecursive<T> = T | ObjectContaining<T> | AsymmetricMatcher | {
type Func = (...args: any[]) => any;
// Use trick with prototype to allow abstract classes.
// More info: https://stackoverflow.com/a/38642922/2009373
type Constructor = Function & { prototype: any };
type ImplementationCallback = (() => PromiseLike<any>) | ((done: DoneFn) => void);
type ExpectedRecursive<T> = T | ObjectContaining<T> | AsymmetricMatcher<any> | {
[K in keyof T]: ExpectedRecursive<T[K]> | Any;
};
type Expected<T> = T | ObjectContaining<T> | AsymmetricMatcher | Any | Spy | {
type Expected<T> = T | ObjectContaining<T> | AsymmetricMatcher<any> | Any | Spy | {
[K in keyof T]: ExpectedRecursive<T[K]>;
};
type SpyObjMethodNames<T = undefined> =
T extends undefined ?
(ReadonlyArray<string> | {[methodName: string]: any}) :
(ReadonlyArray<keyof T> | {[P in keyof T]?: ReturnType<T[P] extends InferableFunction ? T[P] : any>});
type AnyMethods<T> = {
[K in {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T]]: InferableFunction
};
(ReadonlyArray<string> | { [methodName: string]: any }) :
(ReadonlyArray<keyof T> | { [P in keyof T]?: T[P] extends Func ? ReturnType<T[P]> : any });
function clock(): Clock;
var matchersUtil: MatchersUtil;
function any(aclass: any): Any;
/**
* That will succeed if the actual value being compared is an instance of the specified class/constructor.
*/
function any(aclass: Constructor | Symbol): AsymmetricMatcher<any>;
function anything(): Any;
/**
* That will succeed if the actual value being compared is not `null` and not `undefined`.
*/
function anything(): AsymmetricMatcher<any>;
/**
* That will succeed if the actual value being compared is `true` or anything truthy.
* @since 3.1.0
*/
function truthy(): Truthy;
function truthy(): AsymmetricMatcher<any>;
/**
* That will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything falsey.
* @since 3.1.0
*/
function falsy(): Falsy;
function falsy(): AsymmetricMatcher<any>;
/**
* That will succeed if the actual value being compared is empty.
* @since 3.1.0
*/
function empty(): Empty;
function empty(): AsymmetricMatcher<any>;
/**
* That will succeed if the actual value being compared is not empty.
* @since 3.1.0
*/
function notEmpty(): NotEmpty;
function notEmpty(): AsymmetricMatcher<any>;
function arrayContaining<T>(sample: ArrayLike<T>): ArrayContaining<T>;
function arrayWithExactContents<T>(sample: ArrayLike<T>): ArrayContaining<T>;
function objectContaining<T>(sample: Partial<T>): ObjectContaining<T>;
function createSpy<Fun extends InferableFunction>(name?: string, originalFn?: Fun): Spy<Fun>;
function createSpy<Fn extends Func>(name?: string, originalFn?: Fn): Spy<Fn>;
function createSpyObj(baseName: string, methodNames: SpyObjMethodNames): any;
function createSpyObj<T>(baseName: string, methodNames: SpyObjMethodNames<T>): SpyObj<T>;
function createSpyObj(methodNames: SpyObjMethodNames): any;
function createSpyObj<T>(methodNames: SpyObjMethodNames): SpyObj<T>;
function createSpyObj<T>(methodNames: SpyObjMethodNames<T>): SpyObj<T>;
function pp(value: any): string;
@@ -252,11 +263,11 @@ declare namespace jasmine {
function addMatchers(matchers: CustomMatcherFactories): void;
function stringMatching(str: string | RegExp): Any;
function stringMatching(str: string | RegExp): AsymmetricMatcher<string>;
function formatErrorMsg(domain: string, usage: string): (msg: string) => string;
interface Any {
interface Any extends AsymmetricMatcher<any> {
(...params: any[]): any; // jasmine.Any can also be a function
new (expectedClass: any): any;
@@ -264,27 +275,22 @@ declare namespace jasmine {
jasmineToString(): string;
}
interface AsymmetricMatcher<T extends string = string> {
asymmetricMatch(other: any): boolean;
jasmineToString?(): T;
interface AsymmetricMatcher<TValue> {
asymmetricMatch(other: TValue, customTesters: ReadonlyArray<CustomEqualityTester>): boolean;
jasmineToString?(): string;
}
interface Truthy extends AsymmetricMatcher<'<jasmine.truthy>'> { }
interface Falsy extends AsymmetricMatcher<'<jasmine.falsy>'> { }
interface Empty extends AsymmetricMatcher<'<jasmine.empty>'> { }
interface NotEmpty extends AsymmetricMatcher<'<jasmine.notEmpty>'> { }
// taken from TypeScript lib.core.es6.d.ts, applicable to CustomMatchers.contains()
interface ArrayLike<T> {
length: number;
[n: number]: T;
}
interface ArrayContaining<T> extends AsymmetricMatcher {
interface ArrayContaining<T> extends AsymmetricMatcher<any> {
new?(sample: ArrayLike<T>): ArrayLike<T>;
}
interface ObjectContaining<T> {
interface ObjectContaining<T> extends AsymmetricMatcher<any> {
new?(sample: {[K in keyof T]?: any}): {[K in keyof T]?: any};
jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean;
@@ -501,19 +507,33 @@ declare namespace jasmine {
message(): any;
/**
* Expect the actual value to be `===` to the expected value.
*
* @param expected the actual value to be === to the expected value.
* @param expected - The expected value to compare against.
* @param expectationFailOutput
* @example
* expect(thing).toBe(realThing);
*/
toBe(expected: Expected<T>, expectationFailOutput?: any): boolean;
/**
*
* @param expected the actual value to be equal to the expected, using deep equality comparison.
* Expect the actual value to be equal to the expected, using deep equality comparison.
* @param expected - Expected value.
* @param expectationFailOutput
* @example
* expect(bigObject).toEqual({ "foo": ['bar', 'baz'] });
*/
toEqual(expected: Expected<T>, expectationFailOutput?: any): boolean;
/**
* Expect the actual value to match a regular expression.
* @param expected - Value to look for in the string.
* @example
* expect("my string").toMatch(/string$/);
* expect("other string").toMatch("her");
*/
toMatch(expected: string | RegExp, expectationFailOutput?: any): boolean;
toBeDefined(expectationFailOutput?: any): boolean;
toBeUndefined(expectationFailOutput?: any): boolean;
toBeNull(expectationFailOutput?: any): boolean;
@@ -521,7 +541,7 @@ declare namespace jasmine {
toBeTruthy(expectationFailOutput?: any): boolean;
toBeFalsy(expectationFailOutput?: any): boolean;
toHaveBeenCalled(): boolean;
toHaveBeenCalledBefore(expected: Spy): boolean;
toHaveBeenCalledBefore(expected: Func): boolean;
toHaveBeenCalledWith(...params: any[]): boolean;
toHaveBeenCalledTimes(expected: number): boolean;
toContain(expected: any, expectationFailOutput?: any): boolean;
@@ -536,7 +556,17 @@ declare namespace jasmine {
toThrowMatching(predicate: (thrown: any) => boolean): boolean;
toBeNegativeInfinity(expectationFailOutput?: any): boolean;
toBePositiveInfinity(expectationFailOutput?: any): boolean;
toHaveClass(expected: any, expectationFailOutput?: any): boolean;
/**
* Expect the actual value to be a DOM element that has the expected class.
* @since 3.0.0
* @param expected - The class name to test for.
* @example
* var el = document.createElement('div');
* el.className = 'foo bar baz';
* expect(el).toHaveClass('bar');
*/
toHaveClass(expected: string, expectationFailOutput?: any): boolean;
/**
* Add some context for an expect.
@@ -544,18 +574,66 @@ declare namespace jasmine {
*/
withContext(message: string): Matchers<T>;
/**
* Invert the matcher following this expect.
*/
not: Matchers<T>;
Any: Any;
}
interface ArrayLikeMatchers<T> extends Matchers<ArrayLike<T>> {
/**
* Expect the actual value to be `===` to the expected value.
*
* @param expected - The expected value to compare against.
* @param expectationFailOutput
* @example
* expect(thing).toBe(realThing);
*/
toBe(expected: Expected<ArrayLike<T>> | ArrayContaining<T>, expectationFailOutput?: any): boolean;
/**
* Expect the actual value to be equal to the expected, using deep equality comparison.
* @param expected - Expected value.
* @param expectationFailOutput
* @example
* expect(bigObject).toEqual({ "foo": ['bar', 'baz'] });
*/
toEqual(expected: Expected<ArrayLike<T>> | ArrayContaining<T>, expectationFailOutput?: any): boolean;
toContain(expected: Expected<T>, expectationFailOutput?: any): boolean;
/**
* Add some context for an expect.
* @param message - Additional context to show when the matcher fails.
*/
withContext(message: string): ArrayLikeMatchers<T>;
/**
* Invert the matcher following this expect.
*/
not: ArrayLikeMatchers<T>;
}
type MatchableArgs<Fn> = Fn extends (...args: infer P) => any ? { [K in keyof P]: P[K] | AsymmetricMatcher<any> } : never;
interface FunctionMatchers<Fn extends Func> extends Matchers<any> {
toHaveBeenCalled(): boolean;
toHaveBeenCalledBefore(expected: Func): boolean;
toHaveBeenCalledTimes(expected: number): boolean;
toHaveBeenCalledWith(...params: MatchableArgs<Fn>): boolean;
/**
* Add some context for an expect.
* @param message - Additional context to show when the matcher fails.
*/
withContext(message: string): FunctionMatchers<Fn>;
/**
* Invert the matcher following this expect.
*/
not: FunctionMatchers<Fn>;
}
interface NothingMatcher {
nothing(): void;
}
@@ -745,61 +823,67 @@ declare namespace jasmine {
execute(): void;
}
interface Spy<Fn extends InferableFunction = InferableFunction> {
(...params: any[]): any;
interface Spy<Fn extends Func = Func> {
(...params: Parameters<Fn>): ReturnType<Fn>;
and: SpyAnd<Fn>;
calls: Calls<Fn>;
withArgs(...args: any[]): Spy<Fn>;
withArgs(...args: Parameters<Fn>): Spy<Fn>;
}
type SpyObj<T> = T & {
[k in keyof T]: T[k] extends InferableFunction ? T[k] & Spy<T[k]> : T[k];
[K in keyof T]: T[K] extends Func ? T[K] & Spy<T[K]> : T[K];
};
interface SpyAnd<Fun extends InferableFunction> {
/**
* It's like SpyObj, but doesn't verify argument/return types for functions.
* Useful if TS cannot correctly infer type for complex objects.
*/
type NonTypedSpyObj<T> = SpyObj<{ [K in keyof T]: T[K] extends Func ? Func : T[K] }>;
interface SpyAnd<Fn extends Func> {
identity: string;
/** By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation. */
callThrough(): Spy<Fun>;
callThrough(): Spy<Fn>;
/** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */
returnValue(val: ReturnType<Fun>): Spy<Fun>;
returnValue(val: ReturnType<Fn>): Spy<Fn>;
/** By chaining the spy with and.returnValues, all calls to the function will return specific values in order until it reaches the end of the return values list. */
returnValues(...values: Array<ReturnType<Fun>>): Spy<Fun>;
returnValues(...values: Array<ReturnType<Fn>>): Spy<Fn>;
/** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */
callFake(fn: Fun): Spy<Fun>;
callFake(fn: Fn): Spy<Fn>;
/** By chaining the spy with and.throwError, all calls to the spy will throw the specified value. */
throwError(msg: string): Spy;
/** When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub. */
stub(): Spy;
}
interface Calls<Fun extends InferableFunction> {
interface Calls<Fn extends Func> {
/** By chaining the spy with calls.any(), will return false if the spy has not been called at all, and then true once at least one call happens. */
any(): boolean;
/** By chaining the spy with calls.count(), will return the number of times the spy was called */
count(): number;
/** By chaining the spy with calls.argsFor(), will return the arguments passed to call number index */
argsFor(index: number): Parameters<Fun>;
argsFor(index: number): Parameters<Fn>;
/** By chaining the spy with calls.allArgs(), will return the arguments to all calls */
allArgs(): Array<Parameters<Fun>>;
allArgs(): ReadonlyArray<Parameters<Fn>>;
/** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls */
all(): Array<CallInfo<Fun>>;
all(): ReadonlyArray<CallInfo<Fn>>;
/** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call */
mostRecent(): CallInfo<Fun>;
mostRecent(): CallInfo<Fn>;
/** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call */
first(): CallInfo<Fun>;
first(): CallInfo<Fn>;
/** By chaining the spy with calls.reset(), will clears all tracking for a spy */
reset(): void;
}
interface CallInfo<Fun extends InferableFunction> {
interface CallInfo<Fn extends Func> {
/** The context (the this) for the call */
object: any;
/** All arguments passed to the call */
args: Parameters<Fun>;
args: Parameters<Fn>;
/** The return value of the call */
returnValue: ReturnType<Fun>;
returnValue: ReturnType<Fn>;
}
interface Util {

View File

@@ -15,23 +15,29 @@ describe("A suite is just a function", () => {
});
});
describe("The 'toBe' matcher compares with ===", () => {
it("and has a positive case", () => {
expect(true).toBe(true);
});
it("and can have a negative case", () => {
expect(false).not.toBe(true);
});
});
describe("Included matchers:", () => {
it("The 'toBe' matcher compares with ===", () => {
const a = 12;
const b = a;
describe('toBe', () => {
it("and has a positive case", () => {
expect(true).toBe(true);
});
expect(a).toBe(b);
expect(a).not.toBe(24);
it("and can have a negative case", () => {
expect(false).not.toBe(true);
});
it("the 'toBe' matcher compares with ===", () => {
const a = 12;
const b = a;
expect(a).toBe(b);
expect(a).not.toBe(24);
});
it('should allow to accept any union type', () => {
const value: number | string = null as any;
expect(value).toBe(12);
});
});
describe("The 'toEqual' matcher", () => {
@@ -211,7 +217,9 @@ describe("toBePositiveInfinity", () => {
});
describe("toHaveClass", () => {
expect("").toHaveClass(Array);
const element: HTMLElement = null!;
expect(element).toHaveClass("some-class");
expect(element).toHaveClass(Element); // $ExpectError
});
describe("A spec", () => {
@@ -398,22 +406,19 @@ describe("A spy, when configured to call through", () => {
});
describe("A spy, when configured to fake a return value", () => {
var foo: any, bar: any, fetchedBar: any;
beforeEach(() => {
foo = {
setBar: (value: any) => {
bar = value;
},
getBar: () => {
return bar;
}
};
var bar: number;
const foo = {
setBar: (value: number) => {
bar = value;
},
getBar: () => {
return bar;
}
};
it("verifies return value type", () => {
spyOn(foo, "getBar").and.returnValue(745);
foo.setBar(123);
fetchedBar = foo.getBar();
spyOn(foo, "getBar").and.returnValue("42"); // $ExpectError
});
it("tracks that the spy was called", () => {
@@ -425,6 +430,8 @@ describe("A spy, when configured to fake a return value", () => {
});
it("when called returns the requested value", () => {
const fetchedBar = foo.getBar();
expect(fetchedBar).toEqual(745);
});
});
@@ -722,6 +729,29 @@ describe("A spy, when created manually", () => {
});
});
describe("Spy for generic method", () => {
interface Test {
method<T>(): Array<Box<T>>;
}
interface Box<T> {
value: T;
}
it("should allow to configure generic method", () => {
const spy = jasmine.createSpyObj<Test>('test', ['method']);
spy.method.and.returnValue([{ value: 1 }, { value: 2 }]);
});
it("should allow to configure generic method with non-named spy", () => {
const spy = jasmine.createSpyObj<Test>(['method']);
jasmine.createSpyObj<Test>(['methodUnknown']); // $ExpectError
spy.method.and.returnValue([{ value: 1 }, { value: 2 }]);
});
});
describe("Multiple spies, when created manually", () => {
class Tape {
private rewindTo: number;
@@ -772,6 +802,12 @@ describe("Multiple spies, when created manually", () => {
it("tracks all the arguments of its calls", () => {
expect(tape.rewind).toHaveBeenCalledWith(0);
expect(tape.rewind).toHaveBeenCalledWith('42'); // $ExpectError
expect(tape.rewind).toHaveBeenCalledWith(jasmine.anything());
expect(tape.rewind).toHaveBeenCalledWith(jasmine.falsy());
expect(tape.rewind).not.toHaveBeenCalledWith(1);
expect(tape.rewind).not.toHaveBeenCalledWith('42'); // $ExpectError
expect(tape.rewind).not.toHaveBeenCalledWith(jasmine.truthy());
});
it("read isPlaying property", () => {
@@ -786,13 +822,19 @@ describe("multiple spies, when created with spyOnAllFunctions", () => {
y: (a: number) => a,
};
spyOnAllFunctions(obj);
const spy = spyOnAllFunctions(obj);
spy.x.and.returnValue(42);
spy.y.and.returnValue(24);
spy.z; // $ExpectError
obj.x(0);
obj.y(1);
expect(obj.x).toHaveBeenCalled();
expect(obj.y).toHaveBeenCalledWith(1);
expect(spy.y).toHaveBeenCalledWith(1);
expect(spy.y).toHaveBeenCalledWith("one"); // $ExpectError
});
});
@@ -806,6 +848,9 @@ describe("jasmine.any", () => {
it("matches any value", () => {
expect({}).toEqual(jasmine.any(Object));
expect(12).toEqual(jasmine.any(Number));
expect(42).toEqual(jasmine.any(42)); // $ExpectError
expect({}).toEqual(jasmine.any({})); // $ExpectError
expect(() => null).toEqual(jasmine.any(Function));
});
it("matches any function", () => {
@@ -819,7 +864,7 @@ describe("jasmine.any", () => {
fn2: (param1: number) => param1,
};
const expected: Test = {
const expected = {
fn1: jasmine.any(Function),
fn2: jasmine.any(Function),
};
@@ -827,6 +872,29 @@ describe("jasmine.any", () => {
expect(a).toEqual(expected);
});
it("matches custom types", () => {
class Test { }
const obj = new Test();
expect(obj).toEqual(jasmine.any(Test));
});
it("matches base abstract class", () => {
abstract class TestClassBase { }
class TestClass extends TestClassBase { }
const obj = new TestClass();
expect(obj).toEqual(jasmine.any(TestClass));
expect(obj).toEqual(jasmine.any(TestClassBase));
});
it("matches symbols", () => {
const sym = Symbol('test symbol');
expect(sym).toEqual(jasmine.any(sym));
});
describe("when used with a spy", () => {
it("is useful for comparing arguments", () => {
const foo = jasmine.createSpy('foo');
@@ -836,6 +904,15 @@ describe("jasmine.any", () => {
expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
});
it("is useful for comparing arguments for typed spy", () => {
const foo = jasmine.createSpy<(num: number, fn: () => boolean) => void>('foo');
foo(12, () => {
return true;
});
expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
});
});
});
@@ -920,7 +997,7 @@ describe("jasmine.objectContaining", () => {
describe("when used with a spy", () => {
it("is useful for comparing arguments", () => {
const callback = jasmine.createSpy('callback');
const callback = jasmine.createSpy<(arg: { bar: string }) => void>('callback');
callback({
bar: "baz"
@@ -951,9 +1028,18 @@ describe("jasmine.arrayContaining", () => {
expect(foo).not.toBe(jasmine.arrayContaining([6]));
});
it("matches read-only array", () => {
const bar: ReadonlyArray<number> = [1, 2, 3, 4];
expect(bar).toEqual(jasmine.arrayContaining([3, 1]));
expect(bar).not.toEqual(jasmine.arrayContaining([6]));
expect(bar).toBe(jasmine.arrayContaining([3, 1]));
});
describe("when used with a spy", () => {
it("is useful when comparing arguments", () => {
const callback = jasmine.createSpy('callback');
const callback = jasmine.createSpy<(numbers: number[]) => void>('callback');
callback([1, 2, 3, 4]);
@@ -980,7 +1066,7 @@ describe("jasmine.arrayWithExactContents", () => {
describe("when used with a spy", () => {
it("is useful when comparing arguments", () => {
const callback = jasmine.createSpy('callback');
const callback = jasmine.createSpy<(arg: number[]) => void>('callback');
callback([1, 2, 3, 4]);
@@ -1274,16 +1360,6 @@ describe('better typed spys', () => {
// $ExpectType (val: string) => Spy<() => string>
spyObj.method.and.returnValue;
});
it('has a way to opt out of inferred function types', () => {
interface I {
f(): string;
f(x: any): number;
}
const spyObject = jasmine.createSpyObj<jasmine.AnyMethods<I>>("spyObject", ["f"]);
spyObject.f.and.returnValue("a string - working");
});
});
});
@@ -1439,7 +1515,34 @@ describe('Static Matcher Test', function() {
})
);
});
});
});
describe("User scenarios", () => {
describe("https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34080", () => {
interface Test {
f(): string;
f(x: any): number;
}
it("has a way to opt out of inferred function types", () => {
const spyObject: jasmine.NonTypedSpyObj<Test> = jasmine.createSpyObj<Test>("spyObject", ["f"]);
spyObject.f.and.returnValue("a string - working");
const spy2 = jasmine.createSpyObj<Test>(['f']);
spy2.f.and.returnValue("can return string" as any);
});
it("should be possible to opt out for spyOn", () => {
const obj: Test = null!;
const spy1: jasmine.Spy = spyOn(obj, "f");
spy1.and.returnValue("can return string");
const spy2 = spyOn(obj, "f");
spy2.and.returnValue("can return string" as any);
});
});
});
(() => {
// from boot.js

View File

@@ -5,7 +5,7 @@
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
/// <reference types="jasmine" />
/// <reference types="jasmine/v2" />
declare function it(expectation: string, assertion?: (done: DoneFn) => Promise<void>, timeout?: number): void;
declare function fit(expectation: string, assertion?: (done: DoneFn) => Promise<void>, timeout?: number): void;

View File

@@ -18,5 +18,5 @@ type Top = typeof top;
declare function expectMatcherProxyTop(x: (arg: Top) => boolean): void;
expectMatcherProxyTop(when.captor());
when.captor(jasmine.any(Number)); // $ExpectType MatcherProxy<Any>
when.captor(jasmine.any(Number)); // $ExpectType MatcherProxy<AsymmetricMatcher<any>>
when.noConflict(); // $ExpectType void