From 18be7de7bd4050dbdf8c77f5f0baee5b8347e8dc Mon Sep 17 00:00:00 2001 From: Moshe Kolodny Date: Wed, 13 Mar 2019 17:49:37 -0400 Subject: [PATCH] Add stronger typings to jasmine spys --- types/jasmine-ajax/jasmine-ajax-tests.ts | 2 +- types/jasmine/package.json | 11 + types/jasmine/ts3.1/index.d.ts | 857 ++++++++++++++ types/jasmine/ts3.1/jasmine-tests.ts | 1358 ++++++++++++++++++++++ types/jasmine/ts3.1/tsconfig.json | 22 + types/jasmine/ts3.1/tslint.json | 13 + 6 files changed, 2262 insertions(+), 1 deletion(-) create mode 100644 types/jasmine/package.json create mode 100644 types/jasmine/ts3.1/index.d.ts create mode 100644 types/jasmine/ts3.1/jasmine-tests.ts create mode 100644 types/jasmine/ts3.1/tsconfig.json create mode 100644 types/jasmine/ts3.1/tslint.json diff --git a/types/jasmine-ajax/jasmine-ajax-tests.ts b/types/jasmine-ajax/jasmine-ajax-tests.ts index cf1db49f28..325a66d14f 100644 --- a/types/jasmine-ajax/jasmine-ajax-tests.ts +++ b/types/jasmine-ajax/jasmine-ajax-tests.ts @@ -462,7 +462,7 @@ describe('FakeRequest', () => { it('ticks the jasmine clock on timeout', () => { const clock = { tick: jasmine.createSpy('tick') }; - spyOn(jasmine, 'clock').and.returnValue(clock); + spyOn(jasmine, 'clock').and.returnValue(clock as any); const request = new this.FakeRequest(); request.open(); diff --git a/types/jasmine/package.json b/types/jasmine/package.json new file mode 100644 index 0000000000..f2591ceb88 --- /dev/null +++ b/types/jasmine/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "types": "index", + "typesVersions": { + ">=3.1.0-0": { + "*": [ + "ts3.1/*" + ] + } + } +} diff --git a/types/jasmine/ts3.1/index.d.ts b/types/jasmine/ts3.1/index.d.ts new file mode 100644 index 0000000000..c5c39b342c --- /dev/null +++ b/types/jasmine/ts3.1/index.d.ts @@ -0,0 +1,857 @@ +// Definitions by: Boris Yankov +// Theodore Brown +// David Pärsson +// Gabe Moothart +// Lukas Zech +// Boris Breuer +// Chris Yungmann +// Giles Roadnight +// Yaroslav Admin +// Domas Trijonis +// Peter Safranek +// Moshe Kolodny +// For ddescribe / iit use : https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts + +type ImplementationCallback = (() => Promise) | ((done: DoneFn) => void); +type InferableFunction = (...args: any[]) => any; + +/** + * Create a group of specs (often called a suite). + * @param description Textual description of the group + * @param specDefinitions Function for Jasmine to invoke that will define inner suites a specs + */ +declare function describe(description: string, specDefinitions: () => void): void; + +/** + * A focused `describe`. If suites or specs are focused, only those that are focused will be executed. + * @param description Textual description of the group + * @param specDefinitions Function for Jasmine to invoke that will define inner suites a specs + */ +declare function fdescribe(description: string, specDefinitions: () => void): void; + +/** + * A temporarily disabled `describe`. Specs within an xdescribe will be marked pending and not executed. + * @param description Textual description of the group + * @param specDefinitions Function for Jasmine to invoke that will define inner suites a specs + */ +declare function xdescribe(description: string, specDefinitions: () => void): void; + +/** + * Define a single spec. A spec should contain one or more expectations that test the state of the code. + * A spec whose expectations all succeed will be passing and a spec with any failures will fail. + * @param expectation Textual description of what this spec is checking + * @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; + +/** + * A focused `it`. If suites or specs are focused, only those that are focused will be executed. + * @param expectation Textual description of what this spec is checking + * @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; + +/** + * A temporarily disabled `it`. The spec will report as pending and will not be executed. + * @param expectation Textual description of what this spec is checking + * @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; + +/** + * Mark a spec as pending, expectation results will be ignored. + * If you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. + * @param reason Reason the spec is pending. + */ +declare function pending(reason?: string): void; + +/** + * Run some shared setup before each of the specs in the describe in which it is called. + * @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; + +/** + * 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; + +/** + * Run some shared setup once before all of the specs in the describe are run. + * Note: Be careful, sharing the setup from a beforeAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail. + * @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; + +/** + * Run some shared teardown once before all of the specs in the describe are run. + * Note: Be careful, sharing the teardown from a afterAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail. + * @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; + +/** + * Create an expectation for a spec. + * @checkReturnValue see https://tsetse.info/check-return-value + * @param spy + */ +declare function expect(spy: Function): jasmine.Matchers; + +/** + * Create an expectation for a spec. + * @checkReturnValue see https://tsetse.info/check-return-value + * @param actual + */ +declare function expect(actual: ArrayLike): jasmine.ArrayLikeMatchers; + +/** + * Create an expectation for a spec. + * @checkReturnValue see https://tsetse.info/check-return-value + * @param actual Actual computed value to test expectations against. + */ +declare function expect(actual: T): jasmine.Matchers; + +/** + * Create an expectation for a spec. + */ +declare function expect(): jasmine.NothingMatcher; + +/** + * Create an asynchronous expectation for a spec. Note that the matchers + * that are provided by an asynchronous expectation all return promises + * which must be either returned from the spec or waited for using `await` + * in order for Jasmine to associate them with the correct spec. + * @checkReturnValue see https://tsetse.info/check-return-value + * @param actual - Actual computed value to test expectations against. + */ +declare function expectAsync(actual: Promise): jasmine.AsyncMatchers; + +/** + * Explicitly mark a spec as failed. + * @param e Reason for the failure + */ +declare function fail(e?: any): void; + +/** + * Action method that should be called when the async work is complete. + */ +interface DoneFn extends Function { + (): void; + + /** fails the spec and indicates that it has completed. If the message is an Error, Error.message is used */ + fail: (message?: Error | string) => void; +} + +type Methods = { + [K in { + [K in keyof T]: T[K] extends Function ? K : never + }[keyof T]]: T[K] +}; + +/** + * Install a spy onto an existing object. + * @param object The object upon which to install the `Spy`. + * @param method The name of the method to replace with a `Spy`. + */ +declare function spyOn( + object: T, method: T[K] extends InferableFunction ? K : never, +): jasmine.Spy; + +/** + * Install a spy on a property installed with `Object.defineProperty` onto an existing object. + * @param object The object upon which to install the `Spy`. + * @param property The name of the property to replace with a `Spy`. + * @param accessType The access type (get|set) of the property to `Spy` on. + */ +declare function spyOnProperty(object: T, property: keyof T, accessType?: 'get' | 'set'): jasmine.Spy; + +/** + * 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 runs(asyncMethod: Function): void; +declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void; +declare function waits(timeout?: number): void; + +declare namespace jasmine { + type Expected = T | ObjectContaining | Any | Spy; + type SpyObjMethodNames = + T extends undefined ? + (ReadonlyArray | {[methodName: string]: any}) : + (ReadonlyArray | {[P in keyof T]?: ReturnType}); + + function clock(): Clock; + + var matchersUtil: MatchersUtil; + + function any(aclass: any): Any; + + function anything(): Any; + + function arrayContaining(sample: ArrayLike): ArrayContaining; + function arrayWithExactContents(sample: ArrayLike): ArrayContaining; + function objectContaining(sample: Partial): ObjectContaining; + function createSpy(name?: string, originalFn?: Fun): Spy; + + function createSpyObj(baseName: string, methodNames: SpyObjMethodNames): any; + function createSpyObj(baseName: string, methodNames: SpyObjMethodNames): SpyObj; + + function createSpyObj(methodNames: SpyObjMethodNames): any; + function createSpyObj(methodNames: SpyObjMethodNames): SpyObj; + + function pp(value: any): string; + + function getEnv(): Env; + + function addCustomEqualityTester(equalityTester: CustomEqualityTester): void; + + function addMatchers(matchers: CustomMatcherFactories): void; + + function stringMatching(str: string | RegExp): Any; + + function formatErrorMsg(domain: string, usage: string): (msg: string) => string; + + interface Any { + (...params: any[]): any; // jasmine.Any can also be a function + new (expectedClass: any): any; + + jasmineMatches(other: any): boolean; + jasmineToString(): string; + } + + // taken from TypeScript lib.core.es6.d.ts, applicable to CustomMatchers.contains() + interface ArrayLike { + length: number; + [n: number]: T; + } + + interface ArrayContaining { + new (sample: ArrayLike): ArrayLike; + + asymmetricMatch(other: any): boolean; + jasmineToString(): string; + } + + interface ObjectContaining { + new (sample: Partial): Partial; + + jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean; + jasmineToString(): string; + } + + interface Block { + new (env: Env, func: SpecFunction, spec: Spec): any; + + execute(onComplete: () => void): void; + } + + interface WaitsBlock extends Block { + new (env: Env, timeout: number, spec: Spec): any; + } + + interface WaitsForBlock extends Block { + new (env: Env, timeout: number, latchFunction: SpecFunction, message: string, spec: Spec): any; + } + + interface Clock { + install(): void; + uninstall(): void; + /** Calls to any registered callback are triggered when the clock is ticked forward via the jasmine.clock().tick function, which takes a number of milliseconds. */ + tick(ms: number): void; + mockDate(date?: Date): void; + withMock(func: () => void): void; + } + + type CustomEqualityTester = (first: any, second: any) => boolean | void; + + interface CustomMatcher { + compare(actual: T, expected: T, ...args: any[]): CustomMatcherResult; + compare(actual: any, ...expected: any[]): CustomMatcherResult; + negativeCompare?(actual: T, expected: T, ...args: any[]): CustomMatcherResult; + negativeCompare?(actual: any, ...expected: any[]): CustomMatcherResult; + } + + type CustomMatcherFactory = (util: MatchersUtil, customEqualityTesters: CustomEqualityTester[]) => CustomMatcher; + + interface CustomMatcherFactories { + [index: string]: CustomMatcherFactory; + } + + interface CustomMatcherResult { + pass: boolean; + message?: string; + } + + interface MatchersUtil { + equals(a: any, b: any, customTesters?: CustomEqualityTester[]): boolean; + contains(haystack: ArrayLike | string, needle: any, customTesters?: CustomEqualityTester[]): boolean; + buildFailureMessage(matcherName: string, isNot: boolean, actual: any, ...expected: any[]): string; + } + + interface Env { + currentSpec: Spec; + + matchersClass: Matchers; + + version(): any; + versionString(): string; + nextSpecId(): number; + addReporter(reporter: Reporter | CustomReporter): void; + execute(): void; + describe(description: string, specDefinitions: () => void): Suite; + // ddescribe(description: string, specDefinitions: () => void): Suite; Not a part of jasmine. Angular team adds these + beforeEach(beforeEachFunction: ImplementationCallback, timeout?: number): void; + beforeAll(beforeAllFunction: ImplementationCallback, timeout?: number): void; + currentRunner(): Runner; + afterEach(afterEachFunction: ImplementationCallback, timeout?: number): void; + afterAll(afterAllFunction: ImplementationCallback, timeout?: number): void; + xdescribe(desc: string, specDefinitions: () => void): XSuite; + it(description: string, func: () => void): Spec; + // iit(description: string, func: () => void): Spec; Not a part of jasmine. Angular team adds these + xit(desc: string, func: () => void): XSpec; + compareRegExps_(a: RegExp, b: RegExp, mismatchKeys: string[], mismatchValues: string[]): boolean; + compareObjects_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; + equals_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; + contains_(haystack: any, needle: any): boolean; + addCustomEqualityTester(equalityTester: CustomEqualityTester): void; + addMatchers(matchers: CustomMatcherFactories): void; + specFilter(spec: Spec): boolean; + throwOnExpectationFailure(value: boolean): void; + seed(seed: string | number): string | number; + provideFallbackReporter(reporter: Reporter): void; + throwingExpectationFailures(): boolean; + allowRespy(allow: boolean): void; + randomTests(): boolean; + randomizeTests(b: boolean): void; + clearReporters(): void; + } + + interface FakeTimer { + new (): any; + + reset(): void; + tick(millis: number): void; + runFunctionsWithinRange(oldMillis: number, nowMillis: number): void; + scheduleFunction(timeoutKey: any, funcToCall: () => void, millis: number, recurring: boolean): void; + } + + interface HtmlReporter { + new (): any; + } + + interface HtmlSpecFilter { + new (): any; + } + + interface Result { + type: string; + } + + interface NestedResults extends Result { + description: string; + + totalCount: number; + passedCount: number; + failedCount: number; + + skipped: boolean; + + rollupCounts(result: NestedResults): void; + log(values: any): void; + getItems(): Result[]; + addResult(result: Result): void; + passed(): boolean; + } + + interface MessageResult extends Result { + values: any; + trace: Trace; + } + + interface ExpectationResult extends Result { + matcherName: string; + passed(): boolean; + expected: any; + actual: any; + message: string; + trace: Trace; + } + + interface Order { + new (options: { random: boolean, seed: string }): any; + random: boolean; + seed: string; + sort(items: T[]): T[]; + } + + namespace errors { + class ExpectationFailed extends Error { + constructor(); + + stack: any; + } + } + + interface TreeProcessor { + new (attrs: any): any; + execute: (done: Function) => void; + processTree(): any; + } + + interface Trace { + name: string; + message: string; + stack: any; + } + + interface PrettyPrinter { + new (): any; + + format(value: any): void; + iterateObject(obj: any, fn: (property: string, isGetter: boolean) => void): void; + emitScalar(value: any): void; + emitString(value: string): void; + emitArray(array: any[]): void; + emitObject(obj: any): void; + append(value: any): void; + } + + interface StringPrettyPrinter extends PrettyPrinter { + } + + interface Queue { + new (env: any): any; + + env: Env; + ensured: boolean[]; + blocks: Block[]; + running: boolean; + index: number; + offset: number; + abort: boolean; + + addBefore(block: Block, ensure?: boolean): void; + add(block: any, ensure?: boolean): void; + insertNext(block: any, ensure?: boolean): void; + start(onComplete?: () => void): void; + isRunning(): boolean; + next_(): void; + results(): NestedResults; + } + + interface Matchers { + new (env: Env, actual: T, spec: Env, isNot?: boolean): any; + + env: Env; + actual: T; + spec: Env; + isNot?: boolean; + message(): any; + + /** + * + * @param expected the actual value to be === to the expected value. + * @param expectationFailOutput + */ + toBe(expected: Expected, expectationFailOutput?: any): boolean; + + /** + * + * @param expected the actual value to be equal to the expected, using deep equality comparison. + * @param expectationFailOutput + */ + toEqual(expected: Expected, expectationFailOutput?: any): boolean; + toMatch(expected: string | RegExp, expectationFailOutput?: any): boolean; + toBeDefined(expectationFailOutput?: any): boolean; + toBeUndefined(expectationFailOutput?: any): boolean; + toBeNull(expectationFailOutput?: any): boolean; + toBeNaN(): boolean; + toBeTruthy(expectationFailOutput?: any): boolean; + toBeFalsy(expectationFailOutput?: any): boolean; + toHaveBeenCalled(): boolean; + toHaveBeenCalledBefore(expected: Spy): boolean; + toHaveBeenCalledWith(...params: any[]): boolean; + toHaveBeenCalledTimes(expected: number): boolean; + toContain(expected: any, expectationFailOutput?: any): boolean; + toBeLessThan(expected: number, expectationFailOutput?: any): boolean; + toBeLessThanOrEqual(expected: number, expectationFailOutput?: any): boolean; + toBeGreaterThan(expected: number, expectationFailOutput?: any): boolean; + toBeGreaterThanOrEqual(expected: number, expectationFailOutput?: any): boolean; + toBeCloseTo(expected: number, precision?: any, expectationFailOutput?: any): boolean; + toThrow(expected?: any): boolean; + toThrowError(message?: string | RegExp): boolean; + toThrowError(expected?: new (...args: any[]) => Error, message?: string | RegExp): boolean; + toThrowMatching(predicate: (thrown: any) => boolean): boolean; + toBeNegativeInfinity(expectationFailOutput?: any): boolean; + toBePositiveInfinity(expectationFailOutput?: any): boolean; + toHaveClass(expected: any, expectationFailOutput?: any): boolean; + + /** + * Add some context for an expect. + * @param message - Additional context to show when the matcher fails + */ + withContext(message: string): Matchers; + + not: Matchers; + + Any: Any; + } + + interface ArrayLikeMatchers extends Matchers> { + toBe(expected: Expected> | ArrayContaining, expectationFailOutput?: any): boolean; + toEqual(expected: Expected> | ArrayContaining, expectationFailOutput?: any): boolean; + toContain(expected: Expected, expectationFailOutput?: any): boolean; + not: ArrayLikeMatchers; + } + + interface NothingMatcher { + nothing(): void; + } + + interface AsyncMatchers { + /** + * Expect a promise to be resolved. + * @param expectationFailOutput + */ + toBeResolved(expectationFailOutput?: any): Promise; + + /** + * Expect a promise to be rejected. + * @param expectationFailOutput + */ + toBeRejected(expectationFailOutput?: any): Promise; + + /** + * Expect a promise to be resolved to a value equal to the expected, using deep equality comparison. + * @param expected - Value that the promise is expected to resolve to. + */ + toBeResolvedTo(expected: Expected): Promise; + + /** + * Expect a promise to be rejected with a value equal to the expected, using deep equality comparison. + * @param expected - Value that the promise is expected to be rejected with. + */ + toBeRejectedWith(expected: Expected): Promise; + + /** + * Add some context for an expect. + * @param message - Additional context to show when the matcher fails. + */ + withContext(message: string): AsyncMatchers; + + /** + * Invert the matcher following this expect. + */ + not: AsyncMatchers; + } + + interface Reporter { + reportRunnerStarting(runner: Runner): void; + reportRunnerResults(runner: Runner): void; + reportSuiteResults(suite: Suite): void; + reportSpecStarting(spec: Spec): void; + reportSpecResults(spec: Spec): void; + log(str: string): void; + } + + interface MultiReporter extends Reporter { + addReporter(reporter: Reporter): void; + } + + interface SuiteInfo { + totalSpecsDefined: number; + } + + interface CustomReportExpectation { + matcherName: string; + message: string; + passed: boolean; + stack: string; + } + + interface FailedExpectation extends CustomReportExpectation { + actual: string; + expected: string; + } + + interface PassedExpectation extends CustomReportExpectation { + } + + interface CustomReporterResult { + description: string; + failedExpectations?: FailedExpectation[]; + fullName: string; + id: string; + passedExpectations?: PassedExpectation[]; + pendingReason?: string; + status?: string; + } + + interface RunDetails { + failedExpectations: ExpectationResult[]; + order: Order; + } + + interface CustomReporter { + jasmineStarted?(suiteInfo: SuiteInfo): void; + suiteStarted?(result: CustomReporterResult): void; + specStarted?(result: CustomReporterResult): void; + specDone?(result: CustomReporterResult): void; + suiteDone?(result: CustomReporterResult): void; + jasmineDone?(runDetails: RunDetails): void; + } + + interface Runner { + new (env: Env): any; + + execute(): void; + beforeEach(beforeEachFunction: SpecFunction): void; + afterEach(afterEachFunction: SpecFunction): void; + beforeAll(beforeAllFunction: SpecFunction): void; + afterAll(afterAllFunction: SpecFunction): void; + finishCallback(): void; + addSuite(suite: Suite): void; + add(block: Block): void; + specs(): Spec[]; + suites(): Suite[]; + topLevelSuites(): Suite[]; + results(): NestedResults; + } + + type SpecFunction = (spec?: Spec) => void; + + interface SuiteOrSpec { + id: number; + env: Env; + description: string; + queue: Queue; + } + + interface Spec extends SuiteOrSpec { + new (env: Env, suite: Suite, description: string): any; + + suite: Suite; + + afterCallbacks: SpecFunction[]; + spies_: Spy[]; + + results_: NestedResults; + matchersClass: Matchers; + + getFullName(): string; + results(): NestedResults; + log(arguments: any): any; + runs(func: SpecFunction): Spec; + addToQueue(block: Block): void; + addMatcherResult(result: Result): void; + getResult(): any; + expect(actual: any): any; + waits(timeout: number): Spec; + waitsFor(latchFunction: SpecFunction, timeoutMessage?: string, timeout?: number): Spec; + fail(e?: any): void; + getMatchersClass_(): Matchers; + addMatchers(matchersPrototype: CustomMatcherFactories): void; + finishCallback(): void; + finish(onComplete?: () => void): void; + after(doAfter: SpecFunction): void; + execute(onComplete?: () => void, enabled?: boolean): any; + addBeforesAndAftersToQueue(): void; + explodes(): void; + spyOn(obj: any, methodName: string, ignoreMethodDoesntExist: boolean): Spy; + spyOnProperty(object: any, property: string, accessType?: 'get' | 'set'): Spy; + spyOnAllFunctions(object: any): Spy; + + removeAllSpies(): void; + throwOnExpectationFailure: boolean; + } + + interface XSpec { + id: number; + runs(): void; + } + + interface Suite extends SuiteOrSpec { + new (env: Env, description: string, specDefinitions: () => void, parentSuite: Suite): any; + + parentSuite: Suite; + + getFullName(): string; + finish(onComplete?: () => void): void; + beforeEach(beforeEachFunction: SpecFunction): void; + afterEach(afterEachFunction: SpecFunction): void; + beforeAll(beforeAllFunction: SpecFunction): void; + afterAll(afterAllFunction: SpecFunction): void; + results(): NestedResults; + add(suiteOrSpec: SuiteOrSpec): void; + specs(): Spec[]; + suites(): Suite[]; + children(): any[]; + execute(onComplete?: () => void): void; + } + + interface XSuite { + execute(): void; + } + + interface Spy { + (...params: any[]): any; + + and: SpyAnd; + calls: Calls; + withArgs(...args: any[]): Spy; + } + + type SpyObj = T & { + [k in keyof T]: T[k] extends InferableFunction ? T[k] & Spy : T[k]; + }; + + interface SpyAnd { + 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; + /** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */ + returnValue(val: ReturnType): Spy; + /** 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>): Spy; + /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */ + callFake(fn: Fun): Spy; + /** 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 { + /** 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; + /** By chaining the spy with calls.allArgs(), will return the arguments to all calls */ + allArgs(): Array>; + /** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls */ + all(): Array>; + /** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call */ + mostRecent(): CallInfo; + /** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call */ + first(): CallInfo; + /** By chaining the spy with calls.reset(), will clears all tracking for a spy */ + reset(): void; + } + + interface CallInfo { + /** The context (the this) for the call */ + object: any; + /** All arguments passed to the call */ + args: Parameters; + /** The return value of the call */ + returnValue: ReturnType; + } + + interface Util { + inherit(childClass: Function, parentClass: Function): any; + formatException(e: any): any; + htmlEscape(str: string): string; + argsToArray(args: any): any; + extend(destination: any, source: any): any; + } + + interface JsApiReporter extends Reporter { + started: boolean; + finished: boolean; + result: any; + messages: any; + runDetails: RunDetails; + + new (): any; + + suites(): Suite[]; + summarize_(suiteOrSpec: SuiteOrSpec): any; + results(): any; + resultsForSpec(specId: any): any; + log(str: any): any; + resultsForSpecs(specIds: any): any; + summarizeResult_(result: any): any; + } + + interface Jasmine { + Spec: Spec; + clock: Clock; + util: Util; + } + + var HtmlReporter: HtmlReporter; + var HtmlSpecFilter: HtmlSpecFilter; + + /** + * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. + */ + var DEFAULT_TIMEOUT_INTERVAL: number; + + /** + * Maximum number of array elements to display when pretty printing objects. + * This will also limit the number of keys and values displayed for an object. + * Elements past this number will be ellipised. + */ + var MAX_PRETTY_PRINT_ARRAY_LENGTH: number; + + /** + * Maximum number of charasters to display when pretty printing objects. + * Characters past this number will be ellipised. + */ + var MAX_PRETTY_PRINT_CHARS: number; + + /** + * Maximum object depth the pretty printer will print to. + * Set this to a lower value to speed up pretty printing if you have large objects. + */ + var MAX_PRETTY_PRINT_DEPTH: number; +} + +declare module "jasmine" { + class jasmine { + constructor(options: any); + jasmine: jasmine.Jasmine; + addMatchers(matchers: jasmine.CustomMatcherFactories): void; + addReporter(reporter: jasmine.Reporter): void; + addSpecFile(filePath: string): void; + addSpecFiles(files: string[]): void; + configureDefaultReporter(options: any, ...args: any[]): void; + execute(files?: string[], filterString?: string): any; + exitCodeCompletion(passed: any): void; + loadConfig(config: any): void; + loadConfigFile(configFilePath: any): void; + loadHelpers(): void; + loadSpecs(): void; + onComplete(onCompleteCallback: (passed: boolean) => void): void; + provideFallbackReporter(reporter: jasmine.Reporter): void; + randomizeTests(value?: any): boolean; + seed(value: any): void; + showColors(value: any): void; + stopSpecOnExpectationFailure(value: any): void; + static ConsoleReporter(): any; + env: jasmine.Env; + reportersCount: number; + completionReporter: jasmine.CustomReporter; + reporter: jasmine.CustomReporter; + coreVersion(): string; + showingColors: boolean; + projectBaseDir: string; + printDeprecation(): void; + specFiles: string[]; + helperFiles: string[]; + } + export = jasmine; +} diff --git a/types/jasmine/ts3.1/jasmine-tests.ts b/types/jasmine/ts3.1/jasmine-tests.ts new file mode 100644 index 0000000000..92c2a87494 --- /dev/null +++ b/types/jasmine/ts3.1/jasmine-tests.ts @@ -0,0 +1,1358 @@ +// tests based on http://jasmine.github.io/2.2/introduction.html + +describe("A suite", () => { + it("contains spec with an expectation", () => { + expect(true).toBe(true); + }); +}); + +describe("A suite is just a function", () => { + var a: boolean; + + it("and so is a spec", () => { + a = true; + expect(a).toBe(true); + }); +}); + +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; + + expect(a).toBe(b); + expect(a).not.toBe(24); + }); + + describe("The 'toEqual' matcher", () => { + it("works for simple literals and variables", () => { + const a = 12; + expect(a).toEqual(12); + }); + + it("should work for objects", () => { + const foo = { + a: 12, + b: 34 + }; + const bar = { + a: 12, + b: 34 + }; + expect(foo).toEqual(bar); + }); + + it("should work for optional values", () => { + const opt: string | undefined = Math.random() > .5 ? "s" : undefined; + expect(opt).toEqual(undefined); + }); + }); + + it("The 'toMatch' matcher is for regular expressions", () => { + const message = "foo bar baz"; + + expect(message).toMatch(/bar/); + expect(message).toMatch("bar"); + expect(message).not.toMatch(/quux/); + }); + + it("The 'toBeDefined' matcher compares against `undefined`", () => { + const a = { + foo: "foo" + }; + + expect(a.foo).toBeDefined(); + expect((a as any).bar).not.toBeDefined(); + }); + + it("The `toBeUndefined` matcher compares against `undefined`", () => { + const a = { + foo: "foo" + }; + + expect(a.foo).not.toBeUndefined(); + expect((a as any).bar).toBeUndefined(); + }); + + it("The 'toBeNull' matcher compares against null", () => { + const a: string | null = Math.random() > 0.5 ? "s" : null; + const foo = "foo"; + + expect(null).toBeNull(); + expect(a).toBeNull(); + expect(foo).not.toBeNull(); + }); + + it("The 'toBeTruthy' matcher is for boolean casting testing", () => { + const a: string | undefined = Math.random() > 0.5 ? "s" : undefined; + const foo = "foo"; + + expect(foo).toBeTruthy(); + expect(a).not.toBeTruthy(); + }); + + it("The 'toBeFalsy' matcher is for boolean casting testing", () => { + const a: string | undefined = Math.random() > 0.5 ? "s" : undefined; + const foo = "foo"; + + expect(a).toBeFalsy(); + expect(foo).not.toBeFalsy(); + }); + + it("The 'toContain' matcher is for finding an item in an Array", () => { + const a = ["foo", "bar", "baz"]; + + expect(a).toContain('foo'); + expect(a).not.toContain("quux"); + }); + + it("The 'toContain' matcher is also for finding an object containing distinct properties in an Array", () => { + const a = [{ a: "foo" }, { a: "bar" }, { b: "baz" }]; + + expect(a).toContain(jasmine.objectContaining({ a: "foo" })); + expect(a).not.toContain({ a: "quux" }); + }); + + it("The 'toBeLessThan' matcher is for mathematical comparisons", () => { + const pi = 3.1415926; + const e = 2.78; + + expect(e).toBeLessThan(pi); + expect(pi).not.toBeLessThan(e); + }); + + it("The 'toBeGreaterThan' is for mathematical comparisons", () => { + const pi = 3.1415926; + const e = 2.78; + + expect(pi).toBeGreaterThan(e); + expect(e).not.toBeGreaterThan(pi); + }); + + it("The 'toBeCloseTo' matcher is for precision math comparison", () => { + const pi = 3.1415926; + const e = 2.78; + + expect(pi).not.toBeCloseTo(e, 2); + expect(pi).toBeCloseTo(e, 0); + }); + + it("The 'toThrow' matcher is for testing if a function throws an exception", () => { + const foo = () => { + return 1 + 2; + }; + const bar = () => { + throw new Error("message"); + }; + + expect(foo).not.toThrow(); + expect(foo).toThrow(); + + expect(bar).not.toThrow(); + expect(bar).toThrow(); + }); + + it("The 'toThrowError' matcher is for testing a specific thrown exception", () => { + const foo = () => { + throw new TypeError("foo bar baz"); + }; + + expect(foo).toThrowError("foo bar baz"); + expect(foo).toThrowError(/bar/); + expect(foo).toThrowError(TypeError); + expect(foo).toThrowError(TypeError, "foo bar baz"); + }); + + 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(); + }); + + it("async matchers - not", async () => { + const badness = new Error("badness"); + const malady = new Error("malady"); + await expectAsync(Promise.reject(badness)).not.toBeResolved(); + await expectAsync(Promise.resolve(true)).not.toBeResolvedTo(false); + await expectAsync(Promise.resolve()).not.toBeRejected(); + await expectAsync(Promise.reject(badness)).not.toBeRejectedWith(malady); + await expectAsync(Promise.reject(badness)).not.withContext("additional info").toBeResolved(); + await expectAsync(Promise.reject(badness)).withContext("additional info").not.toBeResolved(); + }); +}); + +describe("toThrowMatching", () => { + expect(() => { + ({} as any).doSomething(); + }).toThrowMatching(error => error !== undefined); +}); + +describe("toBeNegativeInfinity", () => { + expect("").toBeNegativeInfinity(); +}); + +describe("toBePositiveInfinity", () => { + expect("").toBePositiveInfinity(); +}); + +describe("toHaveClass", () => { + expect("").toHaveClass(Array); +}); + +describe("A spec", () => { + it("is just a function, so it can contain any code", () => { + var foo = 0; + foo += 1; + + expect(foo).toEqual(1); + }); + + it("can have more than one expectation", () => { + var foo = 0; + foo += 1; + + expect(foo).toEqual(1); + expect(true).toEqual(true); + }); +}); + +describe("A spec (with setup and tear-down)", () => { + var foo: number; + + beforeEach(() => { + foo = 0; + foo += 1; + }); + + afterEach(() => { + foo = 0; + }); + + it("is just a function, so it can contain any code", () => { + expect(foo).toEqual(1); + }); + + it("can have more than one expectation", () => { + expect(foo).toEqual(1); + expect(true).toEqual(true); + }); +}); + +describe("A spec", () => { + var foo: number; + + beforeEach(() => { + foo = 0; + foo += 1; + }); + + afterEach(() => { + foo = 0; + }); + + it("is just a function, so it can contain any code", () => { + expect(foo).toEqual(1); + }); + + it("can have more than one expectation", () => { + expect(foo).toEqual(1); + expect(true).toEqual(true); + }); + + describe("nested inside a second describe", () => { + var bar: number; + + beforeEach(() => { + bar = 1; + }); + + it("can reference both scopes as needed", () => { + expect(foo).toEqual(bar); + }); + }); +}); + +describe("withContext", () => { + it("can be used after an expectation", () => { + expect(1).withContext('context message').toBe(1); + }); +}); + +xdescribe("A spec", () => { + var foo: number; + + beforeEach(() => { + foo = 0; + foo += 1; + }); + + it("is just a function, so it can contain any code", () => { + expect(foo).toEqual(1); + }); +}); + +describe("Pending specs", () => { + xit("can be declared 'xit'", () => { + expect(true).toBe(false); + }); + + it("can be declared with 'it' but without a function"); + + it("can be declared by calling 'pending' in the spec body", () => { + expect(true).toBe(false); + pending(); // without reason + pending('this is why it is pending'); + }); +}); + +describe("A spy", () => { + var foo: any, bar: any, baz: any = null; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + }, + setBaz: (value: any) => { + baz = value; + } + }; + + spyOn(foo, 'setBar'); + spyOn(foo, 'setBaz'); + + foo.setBar(123); + foo.setBar(456, 'another param'); + foo.setBaz(789); + }); + + it("tracks that the spy was called", () => { + expect(foo.setBar).toHaveBeenCalled(); + }); + + it("tracks all the arguments of its calls", () => { + expect(foo.setBar).toHaveBeenCalledWith(123); + expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); + }); + + it("tracks the order in which spies were called", () => { + expect(foo.setBar).toHaveBeenCalledBefore(foo.setBaz); + }); + + it("stops all execution on a function", () => { + expect(bar).toBeNull(); + }); + + it("tracks if it was called at all", function() { + foo.setBar(); + + expect(foo.setBar.calls.any()).toEqual(true); + }); +}); + +describe("A spy, when configured to call through", () => { + var foo: any, bar: any, fetchedBar: any; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + }, + getBar: () => { + return bar; + } + }; + + spyOn(foo, 'getBar').and.callThrough(); + + foo.setBar(123); + fetchedBar = foo.getBar(); + }); + + it("tracks that the spy was called", () => { + expect(foo.getBar).toHaveBeenCalled(); + }); + + it("should not effect other functions", () => { + expect(bar).toEqual(123); + }); + + it("when called returns the requested value", () => { + expect(fetchedBar).toEqual(123); + }); +}); + +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; + } + }; + + spyOn(foo, "getBar").and.returnValue(745); + + foo.setBar(123); + fetchedBar = foo.getBar(); + }); + + it("tracks that the spy was called", () => { + expect(foo.getBar).toHaveBeenCalled(); + }); + + it("should not effect other functions", () => { + expect(bar).toEqual(123); + }); + + it("when called returns the requested value", () => { + expect(fetchedBar).toEqual(745); + }); +}); + +describe("A spy, when configured to fake a series of return values", () => { + var foo: any, bar: any; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + }, + getBar: () => { + return bar; + } + }; + + spyOn(foo, "getBar").and.returnValues("fetched first", "fetched second"); + + foo.setBar(123); + }); + + it("tracks that the spy was called", () => { + foo.getBar(123); + expect(foo.getBar).toHaveBeenCalled(); + }); + + it("should not affect other functions", () => { + expect(bar).toEqual(123); + }); + + it("when called multiple times returns the requested values in order", () => { + expect(foo.getBar()).toEqual("fetched first"); + expect(foo.getBar()).toEqual("fetched second"); + expect(foo.getBar()).toBeUndefined(); + }); +}); + +describe("A spy, when configured with an alternate implementation", () => { + var foo: any, bar: any, fetchedBar: any; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + }, + getBar: () => { + return bar; + } + }; + + spyOn(foo, "getBar").and.callFake(() => { + return 1001; + }); + + foo.setBar(123); + fetchedBar = foo.getBar(); + }); + + it("tracks that the spy was called", () => { + expect(foo.getBar).toHaveBeenCalled(); + }); + + it("should not effect other functions", () => { + expect(bar).toEqual(123); + }); + + it("when called returns the requested value", () => { + expect(fetchedBar).toEqual(1001); + }); +}); + +describe("A spy, when configured with alternate implementations for specified arguments", () => { + var foo: any, bar: any, fetchedBar: any; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + }, + getBar: () => { + return bar; + } + }; + + spyOn(foo, "getBar") + .withArgs(1, "2") + .and.callFake(() => 1002); + + foo.setBar(123); + fetchedBar = foo.getBar(1, "2"); + }); + + it("tracks that the spy was called", () => { + expect(foo.getBar).toHaveBeenCalled(); + }); + + it("should not effect other functions", () => { + expect(bar).toEqual(123); + }); + + it("when called returns the requested value", () => { + expect(fetchedBar).toEqual(1002); + }); +}); + +describe("A spy, when configured to throw a value", () => { + var foo: any, bar: any; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + } + }; + + spyOn(foo, "setBar").and.throwError("quux"); + }); + + it("throws the value", () => { + expect(() => { + foo.setBar(123); + }).toThrowError("quux"); + }); +}); + +describe("A spy, when configured with multiple actions", () => { + var foo: any, bar: any, fetchedBar: any; + var fakeCalled = false; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + }, + getBar: () => { + return bar; + } + }; + + spyOn(foo, 'getBar').and.callThrough().and.callFake(() => { + fakeCalled = true; + }); + + foo.setBar(123); + fetchedBar = foo.getBar(); + }); + + it("tracks that the spy was called", () => { + expect(foo.getBar).toHaveBeenCalled(); + }); + + it("should not effect other functions", () => { + expect(bar).toEqual(123); + }); + + it("when called returns the requested value", () => { + expect(fetchedBar).toEqual(123); + }); + + it("should have called the fake implementation", () => { + expect(fakeCalled).toEqual(true); + }); +}); + +describe("A spy", () => { + var foo: any, bar: any = null; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + } + }; + + spyOn(foo, 'setBar').and.callThrough(); + }); + + it("can call through and then stub in the same spec", () => { + foo.setBar(123); + expect(bar).toEqual(123); + + foo.setBar.and.stub(); + bar = null; + + foo.setBar(123); + expect(bar).toBe(null); + }); +}); + +describe("A spy", () => { + var foo: any, bar: any = null; + + beforeEach(() => { + foo = { + setBar: (value: any) => { + bar = value; + } + }; + + spyOn(foo, 'setBar'); + }); + + it("tracks if it was called at all", () => { + expect(foo.setBar.calls.any()).toEqual(false); + + foo.setBar(); + + expect(foo.setBar.calls.any()).toEqual(true); + }); + + it("tracks the number of times it was called", () => { + expect(foo.setBar.calls.count()).toEqual(0); + + foo.setBar(); + foo.setBar(); + + expect(foo.setBar.calls.count()).toEqual(2); + }); + + it("tracks the arguments of each call", () => { + foo.setBar(123); + foo.setBar(456, "baz"); + + expect(foo.setBar.calls.argsFor(0)).toEqual([123]); + expect(foo.setBar.calls.argsFor(1)).toEqual([456, "baz"]); + }); + + it("tracks the arguments of all calls", () => { + foo.setBar(123); + foo.setBar(456, "baz"); + + expect(foo.setBar.calls.allArgs()).toEqual([[123], [456, "baz"]]); + }); + + it("can provide the context and arguments to all calls", () => { + foo.setBar(123); + + expect(foo.setBar.calls.all()).toEqual([{ object: foo, args: [123], returnValue: undefined }]); + }); + + it("has a shortcut to the most recent call", () => { + foo.setBar(123); + foo.setBar(456, "baz"); + + expect(foo.setBar.calls.mostRecent()).toEqual({ object: foo, args: [456, "baz"], returnValue: undefined }); + }); + + it("has a shortcut to the first call", () => { + foo.setBar(123); + foo.setBar(456, "baz"); + + expect(foo.setBar.calls.first()).toEqual({ object: foo, args: [123], returnValue: undefined }); + }); + + it("can be reset", () => { + foo.setBar(123); + foo.setBar(456, "baz"); + + expect(foo.setBar.calls.any()).toBe(true); + + foo.setBar.calls.reset(); + + expect(foo.setBar.calls.any()).toBe(false); + }); +}); + +describe("A spy, when created manually", () => { + var whatAmI: any; + + beforeEach(() => { + whatAmI = jasmine.createSpy('whatAmI'); + + whatAmI("I", "am", "a", "spy"); + }); + + it("is named, which helps in error reporting", () => { + expect(whatAmI.and.identity()).toEqual('whatAmI'); + }); + + it("tracks that the spy was called", () => { + expect(whatAmI).toHaveBeenCalled(); + }); + + it("tracks its number of calls", () => { + expect(whatAmI.calls.count()).toEqual(1); + }); + + it("tracks all the arguments of its calls", () => { + expect(whatAmI).toHaveBeenCalledWith("I", "am", "a", "spy"); + }); + + it("allows access to the most recent call", () => { + expect(whatAmI.calls.mostRecent().args[0]).toEqual("I"); + }); +}); + +describe("Multiple spies, when created manually", () => { + class Tape { + private rewindTo: number; + play(): void { } + pause(): void { } + rewind(pos: number): void { + this.rewindTo = pos; + } + stop(): void { } + readonly isPlaying: boolean; // spy obj makes this writable + } + + var tape: Tape; + var tapeSpy: jasmine.SpyObj; + var el: jasmine.SpyObj; + + beforeEach(() => { + tapeSpy = jasmine.createSpyObj('tape', ['play', 'pause', 'stop', 'rewind']); + tape = tapeSpy; + (tape as { isPlaying: boolean }).isPlaying = false; + el = jasmine.createSpyObj('Element', ['hasAttribute']); + + el.hasAttribute.and.returnValue(false); + el.hasAttribute("href"); + + tape.play(); + tape.pause(); + tape.rewind(0); + + tapeSpy.play.and.callThrough(); + tapeSpy.pause.and.callThrough(); + tapeSpy.rewind.and.callThrough(); + }); + + it("creates spies for each requested function", () => { + expect(tape.play).toBeDefined(); + expect(tape.pause).toBeDefined(); + expect(tape.stop).toBeDefined(); + expect(tape.rewind).toBeDefined(); + }); + + it("tracks that the spies were called", () => { + expect(tape.play).toHaveBeenCalled(); + expect(tape.pause).toHaveBeenCalled(); + expect(tape.rewind).toHaveBeenCalled(); + expect(tape.stop).not.toHaveBeenCalled(); + }); + + it("tracks all the arguments of its calls", () => { + expect(tape.rewind).toHaveBeenCalledWith(0); + }); + + it("read isPlaying property", () => { + expect(tape.isPlaying).toBe(false); + }); +}); + +describe("multiple spies, when created with spyOnAllFunctions", () => { + it("spies on all functions", () => { + const obj = { + x: (a: number) => a, + y: (a: number) => a, + }; + + spyOnAllFunctions(obj); + + obj.x(0); + obj.y(1); + + expect(obj.x).toHaveBeenCalled(); + expect(obj.y).toHaveBeenCalledWith(1); + }); +}); + +describe("jasmine.nothing", () => { + it("matches any value", () => { + expect().nothing(); + }); +}); + +describe("jasmine.any", () => { + it("matches any value", () => { + expect({}).toEqual(jasmine.any(Object)); + expect(12).toEqual(jasmine.any(Number)); + }); + + it("matches any function", () => { + interface Test { + fn1(): void; + fn2(param1: number): number; + } + + const a: Test = { + fn1: () => { }, + fn2: (param1: number) => param1, + }; + + const expected: Test = { + fn1: jasmine.any(Function), + fn2: jasmine.any(Function), + }; + + expect(a).toEqual(expected); + }); + + describe("when used with a spy", () => { + it("is useful for comparing arguments", () => { + const foo = jasmine.createSpy('foo'); + foo(12, () => { + return true; + }); + + expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function)); + }); + }); +}); + +describe("jasmine.objectContaining", () => { + interface fooType { + a: number; + b: number; + bar: string; + } + var foo: fooType; + + beforeEach(() => { + foo = { + a: 1, + b: 2, + bar: "baz" + }; + }); + + it("matches objects with the expect key/value pairs", () => { + // not explictly providing the type on objectContaining only guards against + // missmatching types on know properties + expect(foo).not.toEqual(jasmine.objectContaining({ + a: 37, + foo: 2, // <-- this does not cause an error as the compiler cannot infer the type completely + // b: '123', <-- this would cause an error as `b` defined as number in fooType + })); + + // explictly providing the type on objectContaining makes the guard more precise + // as misspelled properties are detected as well + expect(foo).not.toEqual(jasmine.objectContaining({ + bar: '', + // foo: 1, <-- this would cause an error as `foo` is not defined in fooType + })); + }); + + describe("when used with a spy", () => { + it("is useful for comparing arguments", () => { + const callback = jasmine.createSpy('callback'); + + callback({ + bar: "baz" + }); + + expect(callback).toHaveBeenCalledWith(jasmine.objectContaining({ + bar: "baz" + })); + expect(callback).not.toHaveBeenCalledWith(jasmine.objectContaining({ + c: 37 + })); + }); + }); +}); + +describe("jasmine.arrayContaining", () => { + var foo: number[]; + + beforeEach(() => { + foo = [1, 2, 3, 4]; + }); + + it("matches arrays with some of the values", () => { + expect(foo).toEqual(jasmine.arrayContaining([3, 1])); + expect(foo).not.toEqual(jasmine.arrayContaining([6])); + + expect(foo).toBe(jasmine.arrayContaining([3, 1])); + expect(foo).not.toBe(jasmine.arrayContaining([6])); + }); + + describe("when used with a spy", () => { + it("is useful when comparing arguments", () => { + const callback = jasmine.createSpy('callback'); + + callback([1, 2, 3, 4]); + + expect(callback).toHaveBeenCalledWith(jasmine.arrayContaining([4, 2, 3])); + expect(callback).not.toHaveBeenCalledWith(jasmine.arrayContaining([5, 2])); + }); + }); +}); + +describe("jasmine.arrayWithExactContents", () => { + var foo: number[]; + + beforeEach(() => { + foo = [1, 2, 3, 4]; + }); + + it("matches arrays with exactly the same values", () => { + expect(foo).toEqual(jasmine.arrayWithExactContents([1, 2, 3, 4])); + expect(foo).not.toEqual(jasmine.arrayWithExactContents([6])); + + expect(foo).toBe(jasmine.arrayWithExactContents([1, 2, 3, 4])); + expect(foo).not.toBe(jasmine.arrayWithExactContents([6])); + }); + + describe("when used with a spy", () => { + it("is useful when comparing arguments", () => { + const callback = jasmine.createSpy('callback'); + + callback([1, 2, 3, 4]); + + expect(callback).toHaveBeenCalledWith(jasmine.arrayWithExactContents([1, 2, 3, 4])); + expect(callback).not.toHaveBeenCalledWith(jasmine.arrayWithExactContents([5, 2])); + }); + }); +}); + +describe("Manually ticking the Jasmine Clock", () => { + var timerCallback: any; + + beforeEach(() => { + timerCallback = jasmine.createSpy("timerCallback"); + jasmine.clock().install(); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it("causes a timeout to be called synchronously", () => { + setTimeout(() => { + timerCallback(); + }, 100); + + expect(timerCallback).not.toHaveBeenCalled(); + + jasmine.clock().tick(101); + + expect(timerCallback).toHaveBeenCalled(); + }); + + it("causes an interval to be called synchronously", () => { + setInterval(() => { + timerCallback(); + }, 100); + + expect(timerCallback).not.toHaveBeenCalled(); + + jasmine.clock().tick(101); + expect(timerCallback.calls.count()).toEqual(1); + + jasmine.clock().tick(50); + expect(timerCallback.calls.count()).toEqual(1); + + jasmine.clock().tick(50); + expect(timerCallback.calls.count()).toEqual(2); + }); + + describe("Mocking the Date object", () => { + it("mocks the Date object and sets it to a given time", () => { + const baseTime = new Date(2013, 9, 23); + + jasmine.clock().mockDate(baseTime); + + jasmine.clock().tick(50); + expect(new Date().getTime()).toEqual(baseTime.getTime() + 50); + }); + }); +}); + +describe("Asynchronous specs", () => { + var value: number; + beforeEach((done: DoneFn) => { + setTimeout(() => { + value = 0; + done(); + }, 1); + }); + + it("should support async execution of test preparation and expectations", (done: DoneFn) => { + value += 1; + expect(value).toBeGreaterThan(0); + done(); + }); + + describe("long asynchronous specs", () => { + beforeEach((done: DoneFn) => { + done(); + }, 1000); + + it("takes a long time", (done: DoneFn) => { + setTimeout(() => { + done(); + }, 9000); + }, 10000); + + afterEach((done: DoneFn) => { + done(); + }, 1000); + }); +}); + +describe("Fail", () => { + it("should fail test when called without arguments", () => { + fail(); + }); + + it("should fail test when called with a fail message", () => { + fail("The test failed"); + }); + + it("should fail test when called an error", () => { + fail(new Error("The test failed with this error")); + }); +}); + +// test based on http://jasmine.github.io/2.2/custom_equality.html +describe("custom equality", () => { + const myCustomEquality: jasmine.CustomEqualityTester = function(first: any, second: any): boolean | void { + if (typeof first === "string" && typeof second === "string") { + return first[0] === second[1]; + } + }; + + beforeEach(() => { + jasmine.addCustomEqualityTester(myCustomEquality); + }); + + it("should be custom equal", () => { + expect("abc").toEqual("aaa"); + }); + + it("should be custom not equal", () => { + expect("abc").not.toEqual("abc"); + }); +}); + +// test based on http://jasmine.github.io/2.2/custom_matcher.html +var customMatchers: jasmine.CustomMatcherFactories = { + toBeGoofy: (util: jasmine.MatchersUtil, customEqualityTesters: jasmine.CustomEqualityTester[]) => { + return { + compare: (actual: any, expected: any): jasmine.CustomMatcherResult => { + if (expected === undefined) { + expected = ''; + } + const result: jasmine.CustomMatcherResult = { pass: false }; + + result.pass = util.equals(actual.hyuk, "gawrsh" + expected, customEqualityTesters); + + result.message = result.pass ? + `Expected ${actual} not to be quite so goofy` : + `Expected ${actual} to be goofy, but it was not very goofy`; + + return result; + } + }; + }, + toBeWithinRange: (util: jasmine.MatchersUtil, customEqualityTesters: jasmine.CustomEqualityTester[]) => { + return { + compare: (actual: any, floor: number, ceiling: number): jasmine.CustomMatcherResult => { + const pass = actual >= floor && actual <= ceiling; + const message = `expected ${actual} to be within range ${floor}-${ceiling}`; + return { message, pass }; + }, + + negativeCompare: (actual: any, floor: number, ceiling: number): jasmine.CustomMatcherResult => { + const pass = actual < floor && actual > ceiling; + const message = `expected ${actual} not to be within range ${floor}-${ceiling}`; + return { message, pass }; + } + }; + } +}; +// add the custom matchers to interface jasmine.Matchers via TypeScript declaration merging +// if your test files import or export anything, you'll want to use: +// declare global { +// namespace jasmine { +// interface Matchers { +// ... +// } +// } +// } +declare namespace jasmine { + interface Matchers { + toBeGoofy(expected?: Expected): boolean; + toBeWithinRange(expected?: Expected, floor?: number, ceiling?: number): boolean; + } +} + +describe("Custom matcher: 'toBeGoofy'", () => { + beforeEach(() => { + jasmine.addMatchers(customMatchers); + }); + + it("is available on an expectation", () => { + expect({ + hyuk: 'gawrsh' + }).toBeGoofy(); + }); + + it("can take an 'expected' parameter", () => { + expect({ + hyuk: 'gawrsh is fun' + }).toBeGoofy({ hyuk: ' is fun' }); + }); + + it("can take many 'expected' parameters", () => { + expect(2).toBeWithinRange(1, 3); + }); + + it("can use the custom negativeCompare method", () => { + const matcher = customMatchers["toBeWithinRange"](jasmine.matchersUtil, []); + const result = matcher.negativeCompare!(1, 2, 3); + + expect(result.pass).toBe(false); + expect(result.message).toBe("expected 1 not to be within range 2-3"); + }); + + it("can be negated", () => { + expect({ + hyuk: 'this is fun' + }).not.toBeGoofy(); + }); + + it("has a proper message on failure", () => { + const actual = { hyuk: 'this is fun' }; + + const matcher = customMatchers["toBeGoofy"](jasmine.matchersUtil, []); + const result = matcher.compare(actual, null); + + expect(result.pass).toBe(false); + expect(result.message).toBe(`Expected ${actual} to be goofy, but it was not very goofy`); + }); +}); + +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: string) => Spy<(num: number, str: string) => string> + spy.and.returnValue; + }); + it('has a typed calls property', () => { + spy.calls.first().args; // $ExpectType [number, string] + spy.calls.first().returnValue; // $ExpectType string + }); + it('has a typed callFake', () => { + // $ExpectType (fn: (num: number, str: string) => string) => Spy<(num: number, str: string) => string> + 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'); // $ExpectError + + // $ExpectType string + spy.calls.first().returnValue; + }); + it('can allows overriding the generic', () => { + class Base { + service() {} + } + class Super extends Base { + service2() {} + } + spyOn(new Super(), 'service'); + spyOn(new Super(), 'service2'); // $ExpectError + }); + }); + describe('createSpyObj', () => { + it('returns the correct spy types', () => { + const foo = { + method() { + return 'baz'; + }, + value: 'value', + }; + const spyObj = jasmine.createSpyObj('foo', ['method']); + + // $ExpectType (val: string) => Spy<() => string> + spyObj.method.and.returnValue; + }); + }); +}); + +// test based on http://jasmine.github.io/2.5/custom_reporter.html +var myReporter: jasmine.CustomReporter = { + jasmineStarted: (suiteInfo: jasmine.SuiteInfo) => { + console.log("Running suite with " + suiteInfo.totalSpecsDefined); + }, + + suiteStarted: (result: jasmine.CustomReporterResult) => { + console.log(`Suite started: ${result.description} whose full description is: ${result.fullName}`); + }, + + specStarted: (result: jasmine.CustomReporterResult) => { + console.log(`Spec started: ${result.description} whose full description is: ${result.fullName}`); + }, + + specDone: (result: jasmine.CustomReporterResult) => { + console.log(`Spec: ${result.description} was ${result.status}`); + // tslint:disable-next-line:prefer-for-of + for (var i = 0; result.failedExpectations && i < result.failedExpectations.length; i += 1) { + console.log("Failure: " + result.failedExpectations[i].message); + console.log("Actual: " + result.failedExpectations[i].actual); + console.log("Expected: " + result.failedExpectations[i].expected); + console.log(result.failedExpectations[i].stack); + } + console.log(result.passedExpectations && result.passedExpectations.length); + }, + + suiteDone: (result: jasmine.CustomReporterResult) => { + console.log(`Suite: ${result.description} was ${result.status}`); + // tslint:disable-next-line:prefer-for-of + for (var i = 0; result.failedExpectations && i < result.failedExpectations.length; i += 1) { + console.log('AfterAll ' + result.failedExpectations[i].message); + console.log(result.failedExpectations[i].stack); + } + }, + + jasmineDone: (runDetails: jasmine.RunDetails) => { + console.log('Finished suite'); + console.log('Random:', runDetails.order.random); + } +}; + +jasmine.getEnv().addReporter(myReporter); + +describe("Randomize Tests", () => { + it("should allow randomization of the order of tests", () => { + expect(() => { + const env = jasmine.getEnv(); + env.randomizeTests(true); + }).not.toThrow(); + }); + + it("should allow a seed to be passed in for randomization", () => { + expect(() => { + const env = jasmine.getEnv(); + env.randomizeTests(true); + return env.seed(1234); + }).not.toThrow(); + }); +}); + +// Dest spces copied from jasmine project (https://github.com/jasmine/jasmine/blob/master/spec/core/SpecSpec.js) +describe("createSpyObj", function() { + it("should create an object with spy methods and corresponding return values when you call jasmine.createSpyObj() with an object", function() { + const spyObj = jasmine.createSpyObj('BaseName', {method1: 42, method2: 'special sauce'}); + + expect(spyObj.method1()).toEqual(42); + expect(spyObj.method1.and.identity()).toEqual('BaseName.method1'); + + expect(spyObj.method2()).toEqual('special sauce'); + expect(spyObj.method2.and.identity()).toEqual('BaseName.method2'); + }); + + it("should create an object with a bunch of spy methods when you call jasmine.createSpyObj()", function() { + const spyObj = jasmine.createSpyObj('BaseName', ['method1', 'method2']); + + expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function) }); + expect(spyObj.method1.and.identity()).toEqual('BaseName.method1'); + expect(spyObj.method2.and.identity()).toEqual('BaseName.method2'); + }); + + it("should allow you to omit the baseName and takes only an object", function() { + const spyObj = jasmine.createSpyObj({method1: 42, method2: 'special sauce'}); + + expect(spyObj.method1()).toEqual(42); + expect(spyObj.method1.and.identity()).toEqual('unknown.method1'); + + expect(spyObj.method2()).toEqual('special sauce'); + expect(spyObj.method2.and.identity()).toEqual('unknown.method2'); + }); + + it("should allow you to omit the baseName and takes only a list of methods", function() { + const spyObj = jasmine.createSpyObj(['method1', 'method2']); + + expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function) }); + expect(spyObj.method1.and.identity()).toEqual('unknown.method1'); + expect(spyObj.method2.and.identity()).toEqual('unknown.method2'); + }); + + it("should throw if you pass an empty array argument", function() { + expect(function() { + jasmine.createSpyObj('BaseName', []); + }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); + }); + + it("should throw if you pass an empty object argument", function() { + expect(function() { + jasmine.createSpyObj('BaseName', {}); + }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); + }); +}); + +(() => { + // from boot.js + const env = jasmine.getEnv(); + + const htmlReporter = new jasmine.HtmlReporter(); + env.addReporter(htmlReporter); + + const specFilter = new jasmine.HtmlSpecFilter(); + env.specFilter = (spec) => { + return specFilter.matches(spec.getFullName()); + }; + + const currentWindowOnload = window.onload; + window.onload = () => { + if (currentWindowOnload) { + (currentWindowOnload as any)(null); + } + htmlReporter.initialize(); + env.execute(); + }; +})(); + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; +jasmine.MAX_PRETTY_PRINT_DEPTH = 40; diff --git a/types/jasmine/ts3.1/tsconfig.json b/types/jasmine/ts3.1/tsconfig.json new file mode 100644 index 0000000000..d48a4a559a --- /dev/null +++ b/types/jasmine/ts3.1/tsconfig.json @@ -0,0 +1,22 @@ +{ + "files": [ + "index.d.ts", + "jasmine-tests.ts" + ], + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": false, + "baseUrl": "../../", + "typeRoots": ["../../"], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/types/jasmine/ts3.1/tslint.json b/types/jasmine/ts3.1/tslint.json new file mode 100644 index 0000000000..c540ba0129 --- /dev/null +++ b/types/jasmine/ts3.1/tslint.json @@ -0,0 +1,13 @@ +{ + "extends": "dtslint/dt.json", + "rules": { + "ban-types": false, + "no-declare-current-package": false, + "no-empty-interface": false, + "no-single-declare-module": false, + "no-unnecessary-generics": false, + "no-var-keyword": false, + "one-variable-per-declaration": false, + "only-arrow-functions": false + } +}