diff --git a/scripts/typemock/src/arg.ts b/scripts/typemock/src/arg.ts index b8ada3ee61e..828efb7ca1c 100644 --- a/scripts/typemock/src/arg.ts +++ b/scripts/typemock/src/arg.ts @@ -2,11 +2,11 @@ * Represents an argument condition used during verification. */ export class Arg { - private _validate: (value: any) => boolean; - private _message: string; + private _validate: (arg: any) => boolean; + private _message: string | Message | List; private _rest: boolean; - private constructor(condition: (value: any) => boolean, message: string, rest = false) { + private constructor(condition: (arg: any) => boolean, message: string | Message | List, rest = false) { this._validate = condition; this._message = message; this._rest = rest; @@ -15,62 +15,66 @@ export class Arg { /** * Allows any value. */ - public static any(): T & Arg { - return new Arg(() => true, `any`); + public static any() { + return new Arg(_ => true, `any`); } /** * Allows a value that matches the specified condition. - * @param match The condition used to match the value. + * @param constraint The condition used to match the value. */ - public static is(match: (value: T) => boolean): T & Arg { - return new Arg(match, `is`); + public static is(constraint: (arg: T) => boolean) { + return new Arg(constraint, `is`); } /** * Allows only a null value. */ - public static null(): T & Arg { - return new Arg(value => value === null, `null`); + public static null() { + return new Arg(arg => arg === null, `null`); } /** * Allows only a non-null value. */ - public static notNull(): T & Arg { - return Arg.not(Arg.null()); + public static notNull() { + return Arg.not(Arg.null()); } /** * Allows only an undefined value. */ - public static undefined(): T & Arg { - return new Arg(value => value === undefined, `undefined`); + public static undefined() { + return new Arg(arg => arg === undefined, `undefined`); } /** * Allows only a non-undefined value. */ - public static notUndefined(): T & Arg { - return Arg.not(Arg.undefined()); + public static notUndefined() { + return Arg.not(Arg.undefined()); } /** * Allows only an undefined or null value. */ - public static nullOrUndefined(): T & Arg { - return Arg.or(Arg.null(), Arg.undefined()); + public static nullOrUndefined() { + return Arg.or(Arg.null(), Arg.undefined()); } /** * Allows only a non-undefined, non-null value. */ - public static notNullOrUndefined(): T & Arg { - return Arg.not(Arg.nullOrUndefined()); + public static notNullOrUndefined() { + return Arg.not(Arg.nullOrUndefined()); } - public static optional(condition: T | T & Arg): T & Arg { - return Arg.or(condition, Arg.undefined()); + /** + * Allows a value that matches either the specified condition, or `undefined`. + * @param condition The condition to match. + */ + public static optional(condition: T | T & Arg) { + return Arg.or(condition, Arg.undefined()); } /** @@ -78,48 +82,64 @@ export class Arg { * @param min The minimum value. * @param max The maximum value. */ - public static between(min: T, max: T): T & Arg { - return new Arg(value => min <= value && value <= max, `between ${min} and ${max}`); + public static between(min: T, max: T) { + return new Arg(arg => min <= arg && arg <= max, message`between ${min} and ${max}`); } /** * Allows any value in the provided array. */ - public static in(values: T[]): T & Arg { - return new Arg(value => values.indexOf(value) > -1, `in ${values.join(", ")}`); + public static in(values: object & Iterable) { + return new Arg(arg => includes(values, arg), message`in ${list(values, ", ")}`); } /** * Allows any value not in the provided array. */ - public static notIn(values: T[]): T & Arg { - return Arg.not(Arg.in(values)); + public static notIn(values: T[]) { + return Arg.not(Arg.in(values)); } /** * Allows any value that matches the provided pattern. */ - public static match(pattern: RegExp): T & Arg { - return new Arg(value => pattern.test(value), `matches ${pattern}`); + public static match(pattern: RegExp) { + return new Arg(arg => pattern.test(arg), message`matches ${pattern}`); } - public static startsWith(text: string): string & Arg { - return new Arg(value => typeof value === "string" && value.startsWith(text), `starts with ${text}`); + /** + * Allows any string that starts with the specified substring. + */ + public static startsWith(text: string) { + return new Arg(arg => typeof arg === "string" && arg.startsWith(text), message`starts with ${text}`); } - public static endsWith(text: string): string & Arg { - return new Arg(value => typeof value === "string" && value.endsWith(text), `ends with ${text}`); + /** + * Allows any string that ends with the specified substring. + */ + public static endsWith(text: string) { + return new Arg(arg => typeof arg === "string" && arg.endsWith(text), message`ends with ${text}`); } - public static includes(value: string): string & string[] & Arg; - public static includes(value: T): T[] & Arg; - public static includes(value: T): Arg { - return new Arg(value_ => Array.isArray(value_) ? value_.indexOf(value) >= 0 : typeof value_ === "string" && value_.includes("" + value), `contains ${value}`); + /** + * Allows any string that includes the specified substring. + */ + public static stringIncludes(text: string) { + return new Arg(arg => arg.includes(text), message`includes ${text}`); } + /** + * Allows any array that contains the specified value. + */ + public static arrayIncludes(value: T | T & Arg) { + return new Arg(arg => includes(arg, value), message`contains ${value}`); + } + + /** + * Allows an array that matches the specified values (or `Arg` conditions), in the same order. + */ public static array(values: (T | T & Arg)[]): T[] & Arg { - const conditions = values.map(Arg.from); - return new Arg(value => value.length === conditions.length && Arg.validateAll(conditions, value), `array [${conditions.join(", ")}]`); + return new Arg(arg => Array.isArray(arg) && Arg.validateAll(values.map(Arg.from), arg), message`array [${list(values, ", ")}]`); } /** @@ -141,7 +161,7 @@ export class Arg { /** * Allows any value with the provided `typeof` tag. */ - public static typeof(tag: "object"): object & Arg; + public static typeof(tag: "object"): object & null & Arg; /** * Allows any value with the provided `typeof` tag. */ @@ -154,74 +174,93 @@ export class Arg { * Allows any value with the provided `typeof` tag. */ public static typeof(tag: string): T & Arg; - public static typeof(tag: string): any { - return new Arg(value => typeof value === tag, `typeof ${tag}`); + public static typeof(tag: string) { + return new Arg(arg => typeof arg === tag, message`typeof ${tag}`); } + /** + * Allows any `string`. + */ public static string() { return this.typeof("string"); } + + /** + * Allows any `number`. + */ public static number() { return this.typeof("number"); } + + /** + * Allows any `boolean`. + */ public static boolean() { return this.typeof("boolean"); } + + /** + * Allows any `symbol`. + */ public static symbol() { return this.typeof("symbol"); } - public static object() { return this.typeof("object"); } + + /** + * Allows any `object` (including functions but excluding `null`). + */ + public static object() { + return new Arg(arg => typeof arg === "object" && arg !== null || typeof arg === "function", `object`); + } + + /** + * Allows any `Function` value. + */ public static function() { return this.typeof("function"); } /** * Allows any value that is an instance of the provided function. * @param type The expected constructor. */ - public static instanceof(type: TClass): TClass["prototype"] & Arg { - return new Arg(value => value instanceof type, `instanceof ${type.name}`); + public static instanceof(type: TClass) { + return new Arg(arg => arg instanceof type, message`instanceof ${type.name}`); } /** * Allows any value that has the provided property names in its prototype chain. */ - public static has(...names: string[]): T & Arg { - return new Arg(value => names.filter(name => name in value).length === names.length, `has ${names.join(", ")}`); + public static has(...names: string[]) { + return new Arg(arg => count(names, name => has(arg, name)) === names.length, message`has ${list(names, ", ")}`); } /** * Allows any value that has the provided property names on itself but not its prototype chain. */ - public static hasOwn(...names: string[]): T & Arg { - return new Arg(value => names.filter(name => Object.prototype.hasOwnProperty.call(value, name)).length === names.length, `hasOwn ${names.join(", ")}`); + public static hasOwn(...names: string[]) { + return new Arg(arg => count(names, name => hasOwn(arg, name)) === names.length, message`hasOwn ${list(names, ", ")}`); } /** * Allows any value that matches the provided condition for the rest of the arguments in the call. * @param condition The optional condition for each other element. */ - public static rest(condition?: T | (T & Arg)): T & Arg { - if (condition === undefined) { - return new Arg(() => true, `rest`, /*rest*/ true); - } - - const arg = Arg.from(condition); - return new Arg(value => arg._validate(value), `rest ${arg._message}`, /*rest*/ true); + public static rest(condition?: T | (T & Arg)) { + return arguments.length === 0 + ? new Arg(() => true, `rest`, /*rest*/ true) + : new Arg(arg => Arg.from(condition)._validate(arg), message`rest ${condition}`, /*rest*/ true); } /** * Negates a condition. */ - public static not(value: T | (T & Arg)): T & Arg { - const arg = Arg.from(value); - return new Arg(value => !arg._validate(value), `not ${arg._message}`); + public static not(condition: T | (T & Arg)) { + return new Arg(arg => !Arg.from(condition)._validate(arg), message`not ${condition}`); } /** * Combines conditions, where all conditions must be `true`. */ - public static and(...args: ((T & Arg) | T)[]): T & Arg { - const conditions = args.map(Arg.from); - return new Arg(value => conditions.every(condition => condition._validate(value)), conditions.map(condition => condition._message).join(" and ")); + public static and(...conditions: ((T & Arg) | T)[]) { + return new Arg(arg => conditions.every(condition => Arg.from(condition)._validate(arg)), list(conditions, " and ")); } /** * Combines conditions, where any conditions may be `true`. */ - public static or(...args: ((T & Arg) | T)[]): T & Arg { - const conditions = args.map(Arg.from); - return new Arg(value => conditions.some(condition => condition._validate(value)), conditions.map(condition => condition._message).join(" or ")); + public static or(...conditions: ((T & Arg) | T)[]) { + return new Arg(arg => conditions.some(condition => Arg.from(condition)._validate(arg)), list(conditions, " or ")); } /** @@ -229,11 +268,11 @@ export class Arg { * @param value The value to coerce * @returns The condition */ - public static from(value: T): T & Arg { + public static from(value: T) { return value instanceof Arg ? value : value === undefined ? Arg.undefined() : value === null ? Arg.null() : - new Arg(v => is(v, value), JSON.stringify(value)); + new Arg(arg => is(arg, value), new Message(() => JSON.stringify(value))); } /** @@ -266,6 +305,13 @@ export class Arg { return true; } + /** + * Gets the message associated with the provided `Arg`. + */ + public static messageFor(arg: Arg) { + return typeof arg._message === "string" ? arg._message : arg._message.toString(); + } + /** * Gets a string that represents this condition. */ @@ -279,4 +325,103 @@ export class Arg { */ function is(x: any, y: any) { return (x === y) ? (x !== 0 || 1 / x === 1 / y) : (x !== x && y !== y); +} + +function message(strings: TemplateStringsArray, ...args: any[]) { + return args.some(isDeferred) + ? new Message(() => String.raw(strings, ...args.map(formatArg))) + : String.raw(strings, ...args.map(formatArg)); +} + +class Message { + private _callback: () => string; + private _message: string | undefined; + + constructor(callback: () => string) { + this._callback = callback; + } + + public toString(): string { + if (this._message === undefined) this._message = this._callback(); + return this._message; + } +} + +function list(list: Iterable, separator: string) { + return some(list, isDeferred) + ? new List(list, separator) + : formatIterableObject(list, separator); +} + +class List { + private _items: Iterable; + private _separator: string; + private _message: string | undefined; + + constructor(items: Iterable, separator: string) { + this._items = items; + this._separator = separator; + } + + public toString() { + if (this._message === undefined) this._message = formatIterableObject(this._items, this._separator); + return this._message; + } +} + +function isDeferred(arg: any): boolean { + return arg instanceof Message || arg instanceof List || arg instanceof Arg; +} + +function isIterableObject(value: any): value is Iterable { + return typeof value === "object" && value !== null && typeof value[Symbol.iterator] === "function"; +} + +function formatArg(arg: any) { + return isIterableObject(arg) ? formatIterableObject(arg, ", ") : + arg instanceof Arg ? Arg.messageFor(arg) : + arg; +} + +function formatIterableObject(arg: Iterable, separator: string) { + let result = ""; + for (const item of arg) { + if (result) result += separator; + result += item instanceof Arg ? Arg.messageFor(item) : item; + } + return result; +} + +function count(array: T[], predicate: (value: T) => boolean): number { + let result = 0; + for (const item of array) { + if (predicate(item)) result++; + } + return result; +} + +function has(object: any, key: PropertyKey) { + return Reflect.has(object, key); +} + +function hasOwn(object: any, key: PropertyKey) { + return Object.prototype.hasOwnProperty.call(object, key); +} + +function includes(object: Iterable, condition: T) { + if (condition instanceof Arg) { + for (const item of object) if (Arg.validate(condition, item)) return true; + return false; + } + + if (Array.isArray(object)) return object.indexOf(condition) >= 0; + if (object instanceof Set) return object.has(condition); + for (const item of object) if (is(item, condition)) return true; + return false; +} + +function some(object: Iterable, predicate: (a: T) => boolean) { + if (Array.isArray(object)) return object.some(predicate); + for (const item of object) if (predicate(item)) return true; + return false; } \ No newline at end of file diff --git a/src/harness/typemock.ts b/src/harness/typemock.ts index dd91e5c8cae..f64af2bffb3 100644 --- a/src/harness/typemock.ts +++ b/src/harness/typemock.ts @@ -43,1590 +43,4 @@ declare module "typemock_" { export import spy = typemock_.spy; } } -} - -// // typemock library (original sources in scripts/typemock) -// namespace typemock { -// /** -// * Represents an argument condition used during verification. -// */ -// export class Arg { -// private _validate: (value: any) => boolean; -// private _message: string; -// private _rest: boolean; - -// private constructor(condition: (value: any) => boolean, message: string, rest = false) { -// this._validate = condition; -// this._message = message; -// this._rest = rest; -// } - -// /** -// * Allows any value. -// */ -// public static any(): T & Arg { -// return new Arg(() => true, `any`); -// } - -// /** -// * Allows a value that matches the specified condition. -// * @param match The condition used to match the value. -// */ -// public static is(match: (value: T) => boolean): T & Arg { -// return new Arg(match, `is`); -// } - -// /** -// * Allows only a null value. -// */ -// public static null(): T & Arg { -// return new Arg(value => value === null, `null`); -// } - -// /** -// * Allows only a non-null value. -// */ -// public static notNull(): T & Arg { -// return Arg.not(Arg.null()); -// } - -// /** -// * Allows only an undefined value. -// */ -// public static undefined(): T & Arg { -// return new Arg(value => value === undefined, `undefined`); -// } - -// /** -// * Allows only a non-undefined value. -// */ -// public static notUndefined(): T & Arg { -// return Arg.not(Arg.undefined()); -// } - -// /** -// * Allows only an undefined or null value. -// */ -// public static nullOrUndefined(): T & Arg { -// return Arg.or(Arg.null(), Arg.undefined()); -// } - -// /** -// * Allows only a non-undefined, non-null value. -// */ -// public static notNullOrUndefined(): T & Arg { -// return Arg.not(Arg.nullOrUndefined()); -// } - -// public static optional(condition: T | T & Arg): T & Arg { -// return Arg.or(condition, Arg.undefined()); -// } - -// /** -// * Allows any value within the provided range. -// * @param min The minimum value. -// * @param max The maximum value. -// */ -// public static between(min: T, max: T): T & Arg { -// return new Arg(value => min <= value && value <= max, `between ${min} and ${max}`); -// } - -// /** -// * Allows any value in the provided array. -// */ -// public static in(values: T[]): T & Arg { -// return new Arg(value => values.indexOf(value) > -1, `in ${values.join(", ")}`); -// } - -// /** -// * Allows any value not in the provided array. -// */ -// public static notIn(values: T[]): T & Arg { -// return Arg.not(Arg.in(values)); -// } - -// /** -// * Allows any value that matches the provided pattern. -// */ -// public static match(pattern: RegExp): T & Arg { -// return new Arg(value => pattern.test(value), `matches ${pattern}`); -// } - -// public static startsWith(text: string): string & Arg { -// return new Arg(value => typeof value === "string" && value.startsWith(text), `starts with ${text}`); -// } - -// public static endsWith(text: string): string & Arg { -// return new Arg(value => typeof value === "string" && value.endsWith(text), `ends with ${text}`); -// } - -// public static includes(value: string): string & string[] & Arg; -// public static includes(value: T): T[] & Arg; -// public static includes(value: T): Arg { -// return new Arg(_value => Array.isArray(_value) ? _value.indexOf(value) >= 0 : typeof _value === "string" && _value.includes("" + value), `contains ${value}`); -// } - -// public static array(values: (T | T & Arg)[]): T[] & Arg { -// const conditions = values.map(Arg.from); -// return new Arg(value => value.length === conditions.length && Arg.validateAll(conditions, value), `array [${conditions.join(", ")}]`); -// } - -// /** -// * Allows any value with the provided `typeof` tag. -// */ -// public static typeof(tag: "string"): string & Arg; -// /** -// * Allows any value with the provided `typeof` tag. -// */ -// public static typeof(tag: "number"): number & Arg; -// /** -// * Allows any value with the provided `typeof` tag. -// */ -// public static typeof(tag: "boolean"): boolean & Arg; -// /** -// * Allows any value with the provided `typeof` tag. -// */ -// public static typeof(tag: "symbol"): symbol & Arg; -// /** -// * Allows any value with the provided `typeof` tag. -// */ -// public static typeof(tag: "object"): object & Arg; -// /** -// * Allows any value with the provided `typeof` tag. -// */ -// public static typeof(tag: "function"): ((...args: any[]) => any) & Arg; -// /** -// * Allows any value with the provided `typeof` tag. -// */ -// public static typeof(tag: "undefined"): undefined & Arg; -// /** -// * Allows any value with the provided `typeof` tag. -// */ -// public static typeof(tag: string): T & Arg; -// public static typeof(tag: string): any { -// return new Arg(value => typeof value === tag, `typeof ${tag}`); -// } - -// public static string() { return this.typeof("string"); } -// public static number() { return this.typeof("number"); } -// public static boolean() { return this.typeof("boolean"); } -// public static symbol() { return this.typeof("symbol"); } -// public static object() { return this.typeof("object"); } -// public static function() { return this.typeof("function"); } - -// /** -// * Allows any value that is an instance of the provided function. -// * @param type The expected constructor. -// */ -// public static instanceof(type: TClass): TClass["prototype"] & Arg { -// return new Arg(value => value instanceof type, `instanceof ${type.name}`); -// } - -// /** -// * Allows any value that has the provided property names in its prototype chain. -// */ -// public static has(...names: string[]): T & Arg { -// return new Arg(value => names.filter(name => name in value).length === names.length, `has ${names.join(", ")}`); -// } - -// /** -// * Allows any value that has the provided property names on itself but not its prototype chain. -// */ -// public static hasOwn(...names: string[]): T & Arg { -// return new Arg(value => names.filter(name => Object.prototype.hasOwnProperty.call(value, name)).length === names.length, `hasOwn ${names.join(", ")}`); -// } - -// /** -// * Allows any value that matches the provided condition for the rest of the arguments in the call. -// * @param condition The optional condition for each other element. -// */ -// public static rest(condition?: T | (T & Arg)): T & Arg { -// if (condition === undefined) { -// return new Arg(() => true, `rest`, /*rest*/ true); -// } - -// const arg = Arg.from(condition); -// return new Arg(value => arg._validate(value), `rest ${arg._message}`, /*rest*/ true); -// } - -// /** -// * Negates a condition. -// */ -// public static not(value: T | (T & Arg)): T & Arg { -// const arg = Arg.from(value); -// return new Arg(value => !arg._validate(value), `not ${arg._message}`); -// } - -// /** -// * Combines conditions, where all conditions must be `true`. -// */ -// public static and(...args: ((T & Arg) | T)[]): T & Arg { -// const conditions = args.map(Arg.from); -// return new Arg(value => conditions.every(condition => condition._validate(value)), conditions.map(condition => condition._message).join(" and ")); -// } - -// /** -// * Combines conditions, where any conditions may be `true`. -// */ -// public static or(...args: ((T & Arg) | T)[]): T & Arg { -// const conditions = args.map(Arg.from); -// return new Arg(value => conditions.some(condition => condition._validate(value)), conditions.map(condition => condition._message).join(" or ")); -// } - -// /** -// * Ensures the value is a `Condition` -// * @param value The value to coerce -// * @returns The condition -// */ -// public static from(value: T): T & Arg { -// return value instanceof Arg ? value : -// value === undefined ? Arg.undefined() : -// value === null ? Arg.null() : -// new Arg(v => is(v, value), JSON.stringify(value)); -// } - -// /** -// * Validates an argument against a condition -// * @param condition The condition to validate. -// * @param arg The argument to validate against the condition. -// */ -// public static validate(condition: Arg, arg: any): boolean { -// return condition._validate(arg); -// } - -// /** -// * Validates the arguments against the condition. -// * @param conditions The conditions to validate. -// * @param args The arguments for the execution. -// */ -// public static validateAll(conditions: ReadonlyArray, args: ReadonlyArray): boolean { -// const length = Math.max(conditions.length, args.length); -// let conditionIndex = 0; -// let argIndex = 0; -// while (argIndex < length) { -// const condition = conditionIndex < conditions.length ? conditions[conditionIndex] : undefined; -// const arg = argIndex < args.length ? args[argIndex] : undefined; -// if (!condition) return false; -// if (argIndex >= args.length && condition._rest) return true; -// if (!condition._validate(arg)) return false; -// if (!condition._rest) conditionIndex++; -// argIndex++; -// } -// return true; -// } - -// /** -// * Gets a string that represents this condition. -// */ -// public toString(): string { -// return `<${this._message}>`; -// } -// } - -// /** -// * SameValueZero (from ECMAScript spec), which has stricter equality sematics than "==" or "===". -// */ -// function is(x: any, y: any) { -// return (x === y) ? (x !== 0 || 1 / x === 1 / y) : (x !== x && y !== y); -// } - - -// /** -// * Defines the number of times an action must have been executed during verification of a Mock. -// */ -// export class Times { -// private static _none: Times | undefined; -// private static _once: Times | undefined; -// private static _atLeastOnce: Times | undefined; -// private static _atMostOnce: Times | undefined; - -// private _min: number; -// private _max: number; -// private _message: string; - -// private constructor(min: number, max: number, message: string) { -// this._min = min; -// this._max = max; -// this._message = message; -// } - -// /** -// * Expects that an action was never executed. -// * @returns A new `Times` instance. -// */ -// public static none(): Times { -// return this._none || (this._none = new Times(0, 0, `never`)); -// } - -// /** -// * Expects that an action was executed exactly once. -// * @returns A new `Times` instance. -// */ -// public static once(): Times { -// return this._once || (this._once = new Times(1, 1, `exactly once`)); -// } - -// /** -// * Expects that an action was executed at least once. -// * @returns A new `Times` instance. -// */ -// public static atLeastOnce(): Times { -// return this._atLeastOnce || (this._atLeastOnce = new Times(1, Number.MAX_SAFE_INTEGER, `at least once`)); -// } - -// /** -// * Expects that an action was executed at least the specified number of times. -// * @param count The number of times. -// * @returns A new `Times` instance. -// */ -// public static atLeast(count: number): Times { -// return new Times(count, Number.MAX_SAFE_INTEGER, `at least ${count} time(s)`); -// } - -// /** -// * Expects that an action was executed exactly the specified number of times. -// * @param count The number of times. -// * @returns A new `Times` instance. -// */ -// public static exactly(count: number): Times { -// return new Times(count, count, `exactly ${count} time(s)`); -// } - -// /** -// * Expects that an action was executed at most the specified number of times. -// * @param count The number of times. -// * @returns A new `Times` instance. -// */ -// public static atMost(count: number): Times { -// return new Times(0, count, `at most ${count} time(s)`); -// } - -// /** -// * Expects that an action was executed at most once. -// * @returns A new `Times` instance. -// */ -// public static atMostOnce(): Times { -// return this._atMostOnce || (this._atMostOnce = new Times(0, 1, `at most once`)); -// } - -// /** -// * Expects that an action was executed between a range of times, inclusive. -// * @param min The minimum number of times, inclusive. -// * @param max The maximum number of times, inclusive. -// * @returns A new `Times` instance. -// */ -// public static between(min: number, max: number): Times { -// return new Times(min, max, `between ${min} and ${max} time(s)`); -// } - -// /** -// * Validates the number of times an action was executed. -// * @param count The number of times the action was executed. -// * @returns `true` if the provided count was valid; otherwise, `false`. -// */ -// public validate(count: number): boolean { -// if (count < this._min) return false; -// if (count > this._max) return false; -// return true; -// } - -// /** -// * Checks the number of times an action was executed, throwing an error if the count was not valid. -// * @param count The number of times the action was executed. -// * @param message The message to use to begin the check. -// */ -// public check(count: number, message: string): void { -// if (!this.validate(count)) { -// const expectedMessage = this._message === `never` -// ? `Expected to never be executed.` -// : `Expected to be executed ${this._message}.`; -// throw new Error(`${message}\n${expectedMessage} Actually executed ${count} time(s).`); -// } -// } - -// /** -// * Gets the string representation of this object. -// */ -// public toString(): string { -// return `<${this._message}>`; -// } -// } - -// const weakHandler = new WeakMap>(); -// const weakMock = new WeakMap>(); - -// function noop() { /*does nothing*/ } -// const empty = {}; - -// export type Callable = (...args: any[]) => any; - -// export type Constructable = new (...args: any[]) => any; - -// export interface ThisArg { -// this: any; -// } - -// export interface Returns { -// return: U; -// } - -// export interface Fallback { -// fallback: true; -// } - -// export interface Throws { -// throw: any; -// } - -// export interface Callback { -// callback: Callable; -// } - -// export type Setup = -// | Returns & (ThisArg & Callback | ThisArg | Callback) -// | Returns -// | Throws & (ThisArg & Callback | ThisArg | Callback) -// | Throws -// | Fallback & (ThisArg & Callback | ThisArg | Callback) -// | Fallback -// | ThisArg & Callback -// | ThisArg -// | Callback; - -// /** -// * A mock version of another oject -// */ -// export class Mock { -// private _handler: MockHandler; -// private _proxy: T; -// private _revoke: () => void; - -// /** -// * A mock version of another object -// * @param target The object to mock. -// * @param setups Optional setups to use -// */ -// constructor(target: T = {}, setups?: Partial) { -// this._handler = typeof target === "function" -// ? new MockFunctionHandler() -// : new MockHandler(); - -// const { proxy, revoke } = Proxy.revocable(target, this._handler); -// this._proxy = proxy; -// this._revoke = revoke; - -// weakHandler.set(proxy, this._handler); -// weakMock.set(proxy, this); - -// if (setups) { -// this.setup(setups); -// } -// } - -// /** -// * Gets the mock version of the target -// */ -// public get proxy(): T { -// return this._proxy; -// } - -// /** -// * Creates an empty Mock object. -// */ -// public static object() { -// return new Mock({}); -// } - -// /** -// * Creates an empty Mock function. -// */ -// public static function() { -// // arrow functions don't have a [[Construct]] -// return new Mock(function () { /*does nothing*/ }); // tslint:disable-line:only-arrow-functions -// } - -// /** -// * Creates a function spy. -// */ -// public static spy(): Mock; -// /** -// * Creates a spy on an object or function. -// */ -// public static spy(target: T): Mock; -// /** -// * Installs a spy on a method of an object. Use `revoke()` on the result to reset the spy. -// * @param object The object containing a method. -// * @param propertyKey The name of the method on the object. -// */ -// public static spy any }, K extends keyof T>(object: T, propertyKey: K): Spy; -// public static spy any }, K extends keyof T>(object?: T, propertyKey?: K) { -// return object !== undefined && propertyKey !== undefined -// ? new Spy(object, propertyKey) -// : new Mock(object || noop); -// } - -// /** -// * Gets the mock for an object. -// * @param target The target. -// */ -// public static from(target: T) { -// return | undefined>weakMock.get(target); -// } - -// /** -// * Performs setup of the mock object, overriding the target object's functionality with that provided by the setup -// * @param callback A function used to set up a method result. -// * @param result An object used to describe the result of the method. -// * @returns This mock instance. -// */ -// public setup(callback: (value: T) => U, result?: Setup): this; -// /** -// * Performs setup of the mock object, overriding the target object's functionality with that provided by the setup -// * @param setups An object whose members are used instead of the target object. -// * @returns This mock instance. -// */ -// public setup(setups: Partial): this; -// public setup(setup: Partial | ((value: T) => U), result?: Setup): this { -// if (typeof setup === "function") { -// this._handler.setupCall(setup, result); -// } -// else { -// this._handler.setupMembers(setup); -// } -// return this; -// } - -// /** -// * Performs verification that a specific action occurred at least once. -// * @param callback A callback that simulates the expected action. -// * @param message An optional message to use if verification fails. -// * @returns This mock instance. -// */ -// public verify(callback: (value: T) => U, message?: string): this; -// /** -// * Performs verification that a specific action occurred. -// * @param callback A callback that simulates the expected action. -// * @param times The number of times the action should have occurred. -// * @param message An optional message to use if verification fails. -// * @returns This mock instance. -// */ -// public verify(callback: (value: T) => U, times: Times, message?: string): this; -// /** -// * Performs verification that a specific action occurred. -// * @param callback A callback that simulates the expected action. -// * @param times The number of times the action should have occurred. -// * @returns This mock instance. -// */ -// public verify(callback: (value: T) => U, times?: Times | string, message?: string): this { -// if (typeof times === "string") { -// message = times; -// times = undefined; -// } -// if (times === undefined) { -// times = Times.atLeastOnce(); -// } -// this._handler.verify(callback, times, message); -// return this; -// } - -// public revoke() { -// weakMock.delete(this._proxy); -// weakHandler.delete(this._proxy); -// this._handler.revoke(); -// this._revoke(); -// } -// } - -// export class Spy any }, K extends keyof T> extends Mock { -// private _spy: Inject | undefined; - -// constructor(target: T, propertyKey: K) { -// super(target[propertyKey]); -// this._spy = new Inject(target, propertyKey, this.proxy); -// this._spy.install(); -// } - -// public get installed() { -// return this._spy ? this._spy.installed : false; -// } - -// public install() { -// if (!this._spy) throw new Error("Cannot install a revoked spy."); -// this._spy.install(); -// return this; -// } - -// public uninstall() { -// if (this._spy) this._spy.uninstall(); -// return this; -// } - -// public revoke() { -// if (this._spy) { -// this._spy.uninstall(); -// this._spy = undefined; -// } -// super.revoke(); -// } -// } - -// class Recording { -// public static readonly noThisArg = {}; -// public readonly trap: string; -// public readonly name: PropertyKey | undefined; -// public readonly thisArg: any; -// public readonly argArray: ReadonlyArray; -// public readonly newTarget: any; -// public readonly result: Partial & Throws & Fallback> | undefined; -// public readonly callback: Callable | undefined; - -// private _thisCondition: Arg | undefined; -// private _newTargetCondition: Arg | undefined; -// private _conditions: ReadonlyArray | undefined; - -// constructor(trap: string, name: PropertyKey | undefined, thisArg: any, argArray: ReadonlyArray, newTarget: any, result: Partial & Throws & Fallback> | undefined, callback: Callable | undefined) { -// this.trap = trap; -// this.name = name; -// this.thisArg = thisArg; -// this.argArray = argArray || []; -// this.newTarget = newTarget; -// this.result = result; -// this.callback = callback; -// } - -// public get thisCondition() { -// return this._thisCondition || (this._thisCondition = this.thisArg === Recording.noThisArg ? Arg.any() : Arg.from(this.thisArg)); -// } - -// public get newTargetCondition() { -// return this._newTargetCondition || (this._newTargetCondition = Arg.from(this.newTarget)); -// } - -// public get argConditions() { -// return this._conditions || (this._conditions = this.argArray.map(Arg.from)); -// } - -// public get kind() { -// switch (this.trap) { -// case "apply": return "function"; -// case "construct": return "function"; -// case "invoke": return "method"; -// case "get": return "property"; -// case "set": return "property"; -// } -// } - -// public static select(setups: ReadonlyArray, kind: Recording["kind"], name: PropertyKey | undefined) { -// return setups.filter(setup => setup.kind === kind && setup.name === name); -// } - -// public static evaluate(setups: ReadonlyArray | undefined, trap: string, name: PropertyKey | undefined, thisArg: any, argArray: any[], newTarget: any, fallback: () => any) { -// if (setups && setups.length > 0) { -// for (const setup of setups) { -// if (setup.match(trap, name, thisArg, argArray, newTarget)) { -// const callback = setup.callback; -// if (callback) { -// Reflect.apply(callback, thisArg, argArray); -// } - -// const result = setup.getResult(fallback); -// return trap === "set" ? true : result; -// } -// } -// return trap === "set" ? false : undefined; -// } -// return fallback(); -// } - -// public toString(): string { -// return `${this.trap} ${this.name || ""}(${this.argConditions.join(", ")})${this.newTarget ? ` [${this.newTarget.name}]` : ``}`; -// } - -// public match(trap: string, name: PropertyKey | undefined, thisArg: any, argArray: any, newTarget: any) { -// return this.trap === trap -// && this.name === name -// && this.matchThisArg(thisArg) -// && Arg.validateAll(this.argConditions, argArray) -// && Arg.validate(this.newTargetCondition, newTarget); -// } - -// public matchRecording(recording: Recording) { -// return this.match(recording.trap, recording.name, recording.thisArg, recording.argArray, recording.newTarget) -// && this.matchResult(recording.result); -// } - -// private matchThisArg(thisArg: any) { -// return thisArg === Recording.noThisArg -// || Arg.validate(this.thisCondition, thisArg); -// } - -// private matchResult(result: Partial & Throws> | undefined) { -// return !this.result -// || this.result.return === (result && result.return) -// && this.result.throw === (result && result.throw); -// } - -// private getResult(fallback: () => any) { -// if (hasOwn(this.result, "throw")) throw this.result.throw; -// if (hasOwn(this.result, "return")) return this.result.return; -// if (hasOwn(this.result, "fallback")) return this.result.fallback ? fallback() : undefined; -// return undefined; -// } -// } - -// class MockHandler implements ProxyHandler { -// protected readonly overrides = Object.create(/*o*/ null); // tslint:disable-line:no-null-keyword -// protected readonly recordings: Recording[] = []; -// protected readonly setups: Recording[] = []; -// protected readonly methodTargets = new WeakMap(); -// protected readonly methodProxies = new Map(); -// protected readonly methodRevocations = new Set<() => void>(); - -// public get(target: T, name: PropertyKey, receiver: any = target): any { -// const setups = Recording.select(this.setups, "property", name); -// const result: Partial & Throws> = {}; -// const recording = new Recording("get", name, target, [], /*newTarget*/ undefined, result, /*callback*/ undefined); -// this.recordings.push(recording); -// try { -// const value = Recording.evaluate(setups, "get", name, receiver, [], /*newTarget*/ undefined, -// () => Reflect.get(this.getTarget(target, name), name, receiver)); -// return typeof value === "function" ? this.getMethod(name, value) : value; -// } -// catch (e) { -// throw result.throw = e; -// } -// } - -// public set(target: T, name: PropertyKey, value: any, receiver: any = target): boolean { -// if (typeof value === "function" && this.methodTargets.has(value)) { -// value = this.methodTargets.get(value); -// } - -// const setups = Recording.select(this.setups, "property", name); -// const result: Partial & Throws> = {}; -// const recording = new Recording("set", name, target, [value], /*newTarget*/ undefined, result, /*callback*/ undefined); -// this.recordings.push(recording); -// try { -// const success = Recording.evaluate(setups, "set", name, receiver, [value], /*newTarget*/ undefined, -// () => Reflect.set(this.getTarget(target, name), name, value, receiver)); -// result.return = undefined; -// return success; -// } -// catch (e) { -// throw result.throw = e; -// } -// } - -// public invoke(proxy: T, name: PropertyKey, method: Function, argArray: any[]): any { -// const setups = Recording.select(this.setups, "method", name); -// const result: Partial & Throws> = {}; -// const recording = new Recording("invoke", name, proxy, argArray, /*newTarget*/ undefined, result, /*callback*/ undefined); -// this.recordings.push(recording); -// try { -// return Recording.evaluate(setups, "invoke", name, proxy, argArray, /*newTarget*/ undefined, -// () => Reflect.apply(method, proxy, argArray)); -// } -// catch (e) { -// throw result.throw = e; -// } -// } - -// public setupCall(callback: (value: any) => any, result: Setup | undefined) { -// const recording = this.capture(callback, result); -// const existing = this.setups.find(setup => setup.name === recording.name); -// if (existing) { -// if (existing.kind !== recording.kind) { -// throw new Error(`Cannot mix method and property setups for the same member name.`); -// } -// } -// else if (recording.name !== undefined) { -// if (recording.kind === "method") { -// this.defineMethod(recording.name); -// } -// else if (recording.kind === "property") { -// this.defineAccessor(recording.name); -// } -// } - -// this.setups.push(recording); -// } - -// public setupMembers(setup: object) { -// for (const propertyKey of Reflect.ownKeys(setup)) { -// const descriptor = Reflect.getOwnPropertyDescriptor(setup, propertyKey); -// if (descriptor) { -// if (propertyKey in this.overrides) { -// throw new Error(`Property '${propertyKey.toString()}' already exists.`); -// } -// Reflect.defineProperty(this.overrides, propertyKey, descriptor); -// } -// } -// } - -// public verify(callback: (value: T) => U, times: Times, message?: string): void { -// const expectation = this.capture(callback, /*result*/ undefined); - -// let count = 0; -// for (const recording of this.recordings) { -// if (expectation.matchRecording(recording)) { -// count++; -// } -// } - -// times.check(count, message || `An error occured when verifying expectation: ${expectation}`); -// } - -// public getTarget(target: T, name: PropertyKey) { -// return name in this.overrides ? this.overrides : target; -// } - -// public getMethod(name: PropertyKey, value: Function): Function { -// const proxy = this.methodProxies.get(name); -// if (proxy && this.methodTargets.get(proxy) === value) { -// return proxy; -// } -// else { -// const { proxy, revoke } = Proxy.revocable(value, new MethodHandler(name)); -// this.methodProxies.set(name, proxy); -// this.methodRevocations.add(revoke); -// this.methodTargets.set(proxy, value); -// return proxy; -// } -// } - -// public revoke() { -// this.methodRevocations.forEach(revoke => { revoke(); }); -// } - -// protected capture(callback: (value: T) => U, result: Setup | undefined) { -// return this.captureCore(empty, new CapturingHandler(result), callback); -// } - -// protected captureCore(target: T, handler: CapturingHandler, callback: (value: T) => U): Recording { -// const { proxy, revoke } = Proxy.revocable(target, handler); -// try { -// callback(proxy); -// if (!handler.recording) { -// throw new Error("Nothing was captured."); -// } -// return handler.recording; -// } -// finally { -// revoke(); -// } -// } - -// private defineMethod(name: PropertyKey) { -// const setups = this.setups; -// this.setupMembers({ -// [name](...argArray: any[]) { -// return Recording.evaluate(setups, "invoke", name, this, argArray, /*newTarget*/ undefined, noop); -// } -// }); -// } - -// private defineAccessor(name: PropertyKey) { -// const setups = this.setups; -// this.setupMembers({ -// get [name]() { -// return Recording.evaluate(setups, "get", name, this, [], /*newTarget*/ undefined, noop); -// }, -// set [name](value: any) { -// Recording.evaluate(setups, "set", name, this, [value], /*newTarget*/ undefined, noop); -// } -// }); -// } -// } - -// class MockFunctionHandler extends MockHandler { -// public apply(target: T, thisArg: any, argArray: any[]): any { -// const setups = Recording.select(this.setups, "function", /*name*/ undefined); -// const result: Partial & Throws> = {}; -// const recording = new Recording("apply", /*name*/ undefined, thisArg, argArray, /*newTarget*/ undefined, result, /*callback*/ undefined); -// this.recordings.push(recording); -// try { -// return Recording.evaluate(setups, "apply", /*name*/ undefined, thisArg, argArray, /*newTarget*/ undefined, -// () => Reflect.apply(target, thisArg, argArray)); -// } -// catch (e) { -// throw result.throw = e; -// } -// } - -// public construct(target: T, argArray: any[], newTarget?: any): any { -// const setups = Recording.select(this.setups, "function", /*name*/ undefined); -// const result: Partial & Throws> = {}; -// const recording = new Recording("construct", /*name*/ undefined, /*thisArg*/ undefined, argArray, newTarget, result, /*callback*/ undefined); -// this.recordings.push(recording); -// try { -// return Recording.evaluate(setups, "construct", /*name*/ undefined, /*thisArg*/ undefined, argArray, newTarget, -// () => Reflect.construct(target, argArray, newTarget)); -// } -// catch (e) { -// throw result.throw = e; -// } -// } - -// protected capture(callback: (value: T) => U, result: Returns & ThisArg | Returns | Throws & ThisArg | Throws | ThisArg | undefined) { -// return this.captureCore(noop, new CapturingFunctionHandler(result), callback); -// } -// } - -// class MethodHandler { -// public name: PropertyKey; - -// constructor(name: PropertyKey) { -// this.name = name; -// } - -// public apply(target: Function, thisArgument: any, argumentsList: any[]): any { -// const handler = weakHandler.get(thisArgument); -// return handler -// ? handler.invoke(thisArgument, this.name, target, argumentsList) -// : Reflect.apply(target, thisArgument, argumentsList); -// } -// } - -// class CapturingHandler implements ProxyHandler { -// public recording: Recording | undefined; - -// protected readonly callback: Callable | undefined; -// protected readonly thisArg: any; -// protected readonly result: Returns | Throws | Fallback | undefined; - -// constructor(result: Partial & Throws & ThisArg & Callback & Fallback> | undefined) { -// this.thisArg = hasOwn(result, "this") ? result.this : Recording.noThisArg; -// this.callback = hasOwn(result, "callback") ? result.callback : undefined; -// this.result = hasOwn(result, "return") ? { return: result.return } : -// hasOwn(result, "throw") ? { throw: result.throw } : -// hasOwn(result, "fallback") && result.fallback ? { fallback: true } : -// undefined; -// } - -// public get(_target: T, name: PropertyKey, _receiver: any): any { -// this.recording = new Recording("get", name, this.thisArg, [], /*newTarget*/ undefined, this.result, this.callback); -// return (...argArray: any[]) => { this.recording = new Recording("invoke", name, this.thisArg, argArray, /*newTarget*/ undefined, this.result, this.callback); }; -// } - -// public set(_target: T, name: PropertyKey, value: any, _receiver: any): boolean { -// this.recording = new Recording("set", name, this.thisArg, [value], /*newTarget*/ undefined, this.result, this.callback); -// return true; -// } -// } - -// class CapturingFunctionHandler extends CapturingHandler { -// public apply(_target: T, _thisArg: any, argArray: any[]): any { -// this.recording = new Recording("apply", /*name*/ undefined, this.thisArg, argArray, /*newTarget*/ undefined, this.result, this.callback); -// return undefined; -// } - -// public construct(_target: T, argArray: any[], newTarget?: any): any { -// this.recording = new Recording("construct", /*name*/ undefined, /*thisArg*/ undefined, argArray, newTarget, this.result, this.callback); -// return undefined; -// } -// } - -// function hasOwn(object: Partial | undefined, key: K): object is (T | T & never) & { [P in K]: T[P] } { -// return object !== undefined -// && Object.prototype.hasOwnProperty.call(object, key); -// } - -// export interface Immediate { -// readonly kind: "immediate"; -// readonly handle: number; -// readonly callback: (...args: any[]) => void; -// readonly args: ReadonlyArray; -// } - -// export interface Timeout { -// readonly kind: "timeout"; -// readonly handle: number; -// readonly callback: (...args: any[]) => void; -// readonly args: ReadonlyArray; -// } - -// export interface Interval { -// readonly kind: "interval"; -// readonly handle: number; -// readonly callback: (...args: any[]) => void; -// readonly args: ReadonlyArray; -// readonly interval: number; -// } - -// export interface AnimationFrame { -// readonly kind: "frame"; -// readonly handle: number; -// readonly callback: (time: number) => void; -// } - -// export type Timer = Immediate | Timeout | Interval | AnimationFrame; - -// type NonImmediateTimer = Timeout | Interval | AnimationFrame; - -// interface Due { -// timer: T; -// due: number; -// depth?: number; -// enabled?: boolean; -// timeline?: boolean; -// } - -// const MAX_INT32 = 2 ** 31 - 1; -// const MIN_TIMEOUT_VALUE = 4; -// const CLAMP_TIMEOUT_NESTING_LEVEL = 5; - -// /** -// * Programmatic control over timers. -// */ -// export class Timers { -// public static readonly MAX_DEPTH = MAX_INT32; - -// private _nextHandle = 1; -// private _immediates = new Map>(); -// private _timeouts = new Map>(); -// private _intervals = new Map>(); -// private _frames = new Map>(); -// private _timeline: Due[] = []; -// private _time: number; -// private _depth = 0; - -// constructor() { -// this._time = 0; - -// // bind each timer method so that it can be detached from this instance. -// this.setImmediate = this.setImmediate.bind(this); -// this.clearImmedate = this.clearImmedate.bind(this); -// this.setTimeout = this.setTimeout.bind(this); -// this.clearTimeout = this.clearTimeout.bind(this); -// this.setInterval = this.setInterval.bind(this); -// this.clearInterval = this.clearInterval.bind(this); -// this.requestAnimationFrame = this.requestAnimationFrame.bind(this); -// this.cancelAnimationFrame = this.cancelAnimationFrame.bind(this); -// } - -// /** -// * Get the current time. -// */ -// public get time(): number { -// return this._time; -// } - -// /** -// * Gets the time of the last scheduled timer (not including repeating intervals). -// */ -// public get endTime(): number { -// return this._timeline && this._timeline.length > 0 -// ? this._timeline[this._timeline.length - 1].due -// : this._time; -// } - -// /** -// * Gets the estimated time remaining. -// */ -// public get remainingTime(): number { -// return this.endTime - this.time; -// } - -// public getPending(options: { kind: "immediate", ms?: number }): Immediate[]; -// public getPending(options: { kind: "timeout", ms?: number }): Timeout[]; -// public getPending(options: { kind: "interval", ms?: number }): Interval[]; -// public getPending(options: { kind: "frame", ms?: number }): AnimationFrame[]; -// public getPending(options?: { kind?: Timer["kind"], ms?: number }): Timer[]; -// public getPending(options: { kind?: Timer["kind"], ms?: number } = {}): Timer[] { -// const { kind, ms = 0 } = options; -// if (ms < 0) throw new TypeError("Argument 'ms' out of range."); - -// const dueTimers: Due[] = []; - -// if (!kind || kind === "immediate") { -// this.copyImmediates(dueTimers); -// } - -// if (kind !== "immediate") { -// this.copyTimelineBefore(dueTimers, this._time + ms, kind); -// } - -// return dueTimers.map(dueTimer => dueTimer.timer); -// } - -// /** -// * Advance the current time and trigger callbacks, returning the number of callbacks triggered. -// * @param ms The number of milliseconds to advance. -// * @param maxDepth The maximum depth for nested `setImmediate` calls to continue processing. -// * - Use `0` (default) to disable processing of nested `setImmediate` calls. -// * - Use `Timers.MAX_DEPTH` to continue processing nested `setImmediate` calls up to the maximum depth. -// */ -// public advance(ms: number, maxDepth = 0): number { -// if (ms < 0) throw new TypeError("Argument 'ms' out of range."); -// if (maxDepth < 0) throw new TypeError("Argument 'maxDepth' out of range."); -// let count = 0; -// const endTime = this._time + (ms | 0); -// while (true) { -// if (maxDepth >= 0) { -// count += this.executeImmediates(maxDepth); -// maxDepth--; -// } - -// const dueTimer = this.dequeueIfBefore(endTime); -// if (dueTimer) { -// this._time = dueTimer.due; -// this.executeTimer(dueTimer); -// count++; -// } -// else { -// this._time = endTime; -// return count; -// } -// } -// } - -// /** -// * Advance the current time to the estimated end time and trigger callbacks, returning the number of callbacks triggered. -// * @param maxDepth The maximum depth for nested `setImmediate` calls to continue processing. -// * - Use `0` (default) to disable processing of nested `setImmediate` calls. -// * - Use `Timers.MAX_DEPTH` to continue processing nested `setImmediate` calls up to the maximum depth. -// */ -// public advanceToEnd(maxDepth = 0) { -// return this.advance(this.remainingTime, maxDepth); -// } - -// /** -// * Execute any pending immediate timers, returning the number of timers triggered. -// * @param maxDepth The maximum depth for nested `setImmediate` calls to continue processing. -// * - Use `0` (default) to disable processing of nested `setImmediate` calls. -// * - Use `Timers.MAX_DEPTH` to continue processing nested `setImmediate` calls up to the maximum depth. -// */ -// public executeImmediates(maxDepth = 0): number { -// if ((maxDepth |= 0) < 0) throw new TypeError("Argument 'maxDepth' out of range."); -// const dueTimers: Due[] = []; -// this.copyImmediates(dueTimers); -// let count = this.executeTimers(dueTimers); -// for (let depth = 0; depth < maxDepth && this._immediates.size > 0; depth++) { -// count += this.executeImmediates(); -// } -// return count; -// } - -// public setImmediate(callback: (...args: any[]) => void, ...args: any[]): any { -// if (this._depth >= Timers.MAX_DEPTH) { -// throw new Error("callback nested too deeply."); -// } - -// const timer: Immediate = { kind: "immediate", handle: this._nextHandle++, callback, args }; -// const dueTimer: Due = { timer, due: -1 }; -// this.addTimer(this._immediates, dueTimer); -// return timer.handle; -// } - -// public clearImmedate(timerId: any): void { -// const dueTimer = this._immediates.get(timerId); -// if (dueTimer) { -// this.deleteTimer(this._immediates, dueTimer); -// } -// } - -// public setTimeout(callback: (...args: any[]) => void, timeout: number, ...args: any[]): any { -// if (this._depth >= Timers.MAX_DEPTH) { -// throw new Error("callback nested too deeply."); -// } - -// if ((timeout |= 0) < 0) timeout = 0; - -// if (this._depth >= CLAMP_TIMEOUT_NESTING_LEVEL && timeout < MIN_TIMEOUT_VALUE) { -// timeout = MIN_TIMEOUT_VALUE; -// } - -// const timer: Timeout = { kind: "timeout", handle: this._nextHandle++, callback, args }; -// const dueTimer: Due = { timer, due: this._time + timeout }; -// this.addTimer(this._timeouts, dueTimer); -// this.addToTimeline(dueTimer); -// return timer.handle; -// } - -// public clearTimeout(timerId: any): void { -// const dueTimer = this._timeouts.get(timerId); -// if (dueTimer) { -// this.deleteTimer(this._timeouts, dueTimer); -// this.removeFromTimeline(dueTimer); -// } -// } - -// public setInterval(callback: (...args: any[]) => void, interval: number, ...args: any[]): any { -// if (this._depth >= Timers.MAX_DEPTH) { -// throw new Error("callback nested too deeply."); -// } - -// if ((interval |= 0) < 10) interval = 10; -// const timer: Interval = { kind: "interval", handle: this._nextHandle++, callback, args, interval }; -// const dueTimer: Due = { timer, due: this._time + interval }; -// this.addTimer(this._intervals, dueTimer); -// this.addToTimeline(dueTimer); -// return timer.handle; -// } - -// public clearInterval(timerId: any): void { -// const dueTimer = this._intervals.get(timerId); -// if (dueTimer) { -// this.deleteTimer(this._intervals, dueTimer); -// this.removeFromTimeline(dueTimer); -// } -// } - -// public requestAnimationFrame(callback: (time: number) => void): any { -// if (this._depth >= Timers.MAX_DEPTH) { -// throw new Error("callback nested too deeply."); -// } - -// const timer: AnimationFrame = { kind: "frame", handle: this._nextHandle++, callback }; -// const dueTimer: Due = { timer, due: this.nextFrameDueTime() }; -// this.addTimer(this._frames, dueTimer); -// this.addToTimeline(dueTimer); -// return timer.handle; -// } - -// public cancelAnimationFrame(timerId: any): void { -// const dueTimer = this._frames.get(timerId); -// if (dueTimer) { -// this.deleteTimer(this._frames, dueTimer); -// this.removeFromTimeline(dueTimer); -// } -// } - -// private nextFrameDueTime() { -// return this._time + this.nextFrameDelta(); -// } - -// private nextFrameDelta() { -// return 16 - this._time % 16; -// } - -// private addTimer(timers: Map>, dueTimer: Due) { -// if (dueTimer.enabled) return; -// timers.set(dueTimer.timer.handle, dueTimer); -// dueTimer.depth = this._depth + 1; -// dueTimer.enabled = true; -// } - -// private deleteTimer(timers: Map>, dueTimer: Due) { -// if (!dueTimer.enabled) return; -// timers.delete(dueTimer.timer.handle); -// dueTimer.enabled = false; -// } - -// private executeTimers(dueTimers: Due[]) { -// let count = 0; -// for (const dueTimer of dueTimers) { -// this.executeTimer(dueTimer); -// count++; -// } -// return count; -// } - -// private executeTimer(dueTimer: Due) { -// switch (dueTimer.timer.kind) { -// case "immediate": return this.executeImmediate(>dueTimer); -// case "timeout": return this.executeTimeout(>dueTimer); -// case "interval": return this.executeInterval(>dueTimer); -// case "frame": return this.executeAnimationFrame(>dueTimer); -// } -// } - -// private executeImmediate(dueTimer: Due) { -// if (!dueTimer.enabled) return; - -// this.deleteTimer(this._immediates, dueTimer); -// this.executeCallback(dueTimer.depth, dueTimer.timer.callback, ...dueTimer.timer.args); -// } - -// private executeTimeout(dueTimer: Due) { -// if (!dueTimer.enabled) return; - -// this.deleteTimer(this._timeouts, dueTimer); -// this.removeFromTimeline(dueTimer); -// this.executeCallback(dueTimer.depth, dueTimer.timer.callback, ...dueTimer.timer.args); -// } - -// private executeInterval(dueTimer: Due) { -// if (!dueTimer.enabled) return; - -// this.removeFromTimeline(dueTimer); -// this.executeCallback(dueTimer.depth, dueTimer.timer.callback, ...dueTimer.timer.args); - -// if (dueTimer.enabled) { -// dueTimer.due += dueTimer.timer.interval; -// this.addToTimeline(dueTimer); -// } -// } - -// private executeAnimationFrame(dueTimer: Due) { -// if (!dueTimer.enabled) return; - -// this.deleteTimer(this._frames, dueTimer); -// this.removeFromTimeline(dueTimer); -// this.executeCallback(dueTimer.depth, dueTimer.timer.callback, this._time); -// } - -// private executeCallback(depth = 0, callback: (...args: any[]) => void, ...args: any[]) { -// const savedDepth = this._depth; -// this._depth = depth; -// try { -// callback(...args); -// } -// finally { -// this._depth = savedDepth; -// } -// } - -// private dequeueIfBefore(dueTime: number) { -// if (this._timeline.length > 0) { -// const dueTimer = this._timeline[0]; -// if (dueTimer.due <= dueTime) { -// this._timeline.shift(); -// dueTimer.timeline = false; -// return dueTimer; -// } -// } -// } - -// private copyImmediates(dueTimers: Due[]) { -// this._immediates.forEach(dueTimer => { dueTimers.push(dueTimer); }); -// } - -// private copyTimelineBefore(dueTimers: Due[], dueTime: number, kind?: Timer["kind"]) { -// for (const dueTimer of this._timeline) { -// if (dueTimer.due <= dueTime && (!kind || dueTimer.timer.kind === kind)) { -// dueTimers.push(dueTimer); -// } -// } -// } - -// private addToTimeline(dueTimer: Due) { -// if (dueTimer.timeline) return; - -// let index = binarySearch(this._timeline, dueTimer, getDueTime, compareTimestamps); -// if (index < 0) { -// index = ~index; -// } -// else { -// while (index < this._timeline.length) { -// if (this._timeline[index].due > dueTimer.due) { -// break; -// } -// index++; -// } -// } - -// insertAt(this._timeline, index, dueTimer); -// dueTimer.timeline = true; -// } - -// private removeFromTimeline(dueTimer: Due) { -// if (dueTimer.timeline) { -// let index = binarySearch(this._timeline, dueTimer, getDueTime, compareTimestamps); -// if (index >= 0) { -// while (index < this._timeline.length) { -// const event = this._timeline[index]; -// if (event === dueTimer) { -// removeAt(this._timeline, index); -// dueTimer.timeline = false; -// return true; -// } -// if (event.due > dueTimer.due) { -// break; -// } -// index++; -// } -// } -// } -// return false; -// } -// } - -// function getDueTime(v: Due) { -// return v.due; -// } - -// function compareTimestamps(a: number, b: number) { -// return a - b; -// } - -// function binarySearch(array: ReadonlyArray, value: T, keySelector: (v: T) => U, keyComparer: (a: U, b: U) => number): number { -// if (array.length === 0) { -// return -1; -// } - -// let low = 0; -// let high = array.length - 1; -// const key = keySelector(value); -// while (low <= high) { -// const middle = low + ((high - low) >> 1); -// const midKey = keySelector(array[middle]); -// const result = keyComparer(midKey, key); -// if (result < 0) { -// low = middle + 1; -// } -// else if (result > 0) { -// high = middle - 1; -// } -// else { -// return middle; -// } -// } - -// return ~low; -// } - -// function removeAt(array: T[], index: number): void { -// if (array.length === 0) { -// return; -// } -// else if (index === 0) { -// array.shift(); -// } -// else if (index === array.length - 1) { -// array.pop(); -// } -// else { -// for (let i = index; i < array.length - 1; i++) { -// array[i] = array[i + 1]; -// } -// array.length--; -// } -// } - -// function insertAt(array: T[], index: number, value: T): void { -// if (index === 0) { -// array.unshift(value); -// } -// else if (index === array.length) { -// array.push(value); -// } -// else { -// for (let i = array.length; i > index; i--) { -// array[i] = array[i - 1]; -// } -// array[index] = value; -// } -// } - -// /** -// * Temporarily injects a value into an object property -// */ -// export class Inject { -// private _target: T; -// private _key: K; -// private _value: any; -// private _originalValue: any; -// private _installed = false; - -// /** -// * Temporarily injects a value into an object property -// * @param target The target object into which to inject a property -// * @param propertyKey The name of the property to inject -// * @param value The value to inject -// */ -// constructor(target: T, propertyKey: K, value?: T[K]) { -// this._target = target; -// this._key = propertyKey; -// this._value = arguments.length === 2 ? target[propertyKey] : value; -// } - -// public get target() { -// return this._target; -// } - -// public get key() { -// return this._key; -// } - -// public get injectedValue(): T[K] { -// return this._installed ? this.currentValue : this._value; -// } - -// public set injectedValue(value: T[K]) { -// if (this._installed) { -// this._target[this._key] = value; -// } -// this._value = value; -// } - -// public get originalValue(): T[K] { -// if (this._installed) { -// return this._originalValue; -// } -// else { -// return this.currentValue; -// } -// } - -// public get currentValue(): T[K] { -// return this._target[this._key]; -// } - -// /** -// * Gets a value indicating whether the Stub is currently installed. -// */ -// public get installed(): boolean { -// return this._installed; -// } - -// /** -// * Installs the stub -// */ -// public install(): void { -// if (this._installed) return; -// this._originalValue = this._target[this._key]; -// this._target[this._key] = this._value; -// this._installed = true; -// } - -// /** -// * Uninstalls the stub -// */ -// public uninstall(): void { -// if (!this._installed) return; -// this._target[this._key] = this._originalValue; -// this._installed = false; -// this._originalValue = undefined; -// } - -// public static exec(target: T, propertyKey: K, value: T[K], action: () => V) { -// const stub = new Inject(target, propertyKey, value); -// return stub.exec(action); -// } - -// /** -// * Executes `action` with the stub installed. -// */ -// public exec(action: () => V): V { -// if (this._installed) { -// return action(); -// } -// try { -// this.install(); -// return action(); -// } -// finally { -// this.uninstall(); -// } -// } -// } - -// /** -// * Creates a spy on an object or function. -// */ -// export function spy(): Mock; -// /** -// * Creates a spy on an object or function. -// */ -// export function spy(target: T): Mock; -// /** -// * Installs a spy on a method of an object. Use `revoke()` on the result to reset the spy. -// * @param object The object containing a method. -// * @param propertyKey The name of the method on the object. -// */ -// export function spy any }, K extends keyof T>(object: T, propertyKey: K): Spy; -// export function spy any }, K extends keyof T>(object?: T, propertyKey?: K) { -// return object === undefined ? Mock.spy() : propertyKey === undefined ? Mock.spy(object) : Mock.spy(object, propertyKey); -// } -// } \ No newline at end of file +} \ No newline at end of file