diff --git a/package.json b/package.json index 583e59a7741..32a477520a8 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "travis-fold": "latest", "ts-node": "latest", "tslint": "latest", + "typemock": "file:scripts/typemock", "typescript": "next", "vinyl": "latest", "xml2js": "^0.4.19" diff --git a/scripts/typemock/gulpfile.js b/scripts/typemock/gulpfile.js index 127ef3dc51e..6e7119ac2d8 100644 --- a/scripts/typemock/gulpfile.js +++ b/scripts/typemock/gulpfile.js @@ -23,7 +23,6 @@ gulp.task("test", ["build"], () => gulp .src(["dist/tests/index.js"], { read: false }) .pipe(mocha({ reporter: "dot" }))); - -gulp.task("watch", ["test"], () => gulp.watch(["src/**/*"], ["test"])); +gulp.task("watch", () => gulp.watch(["src/**/*"], ["test"])); gulp.task("default", ["test"]); \ No newline at end of file diff --git a/scripts/typemock/src/arg.ts b/scripts/typemock/src/arg.ts index 7d6feb5383d..022b31ffb7c 100644 --- a/scripts/typemock/src/arg.ts +++ b/scripts/typemock/src/arg.ts @@ -2,19 +2,21 @@ * Represents an argument condition used during verification. */ export class Arg { - private _condition: (value: any, args: ReadonlyArray, index: number) => { valid: boolean, next?: number }; + private _validate: (value: any) => boolean; private _message: string; + private _rest: boolean; - private constructor(condition: (value: any, args: ReadonlyArray, index: number) => { valid: boolean, next?: number }, message: string) { - this._condition = condition; + 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(() => ({ valid: true }), `any`); + return new Arg(() => true, `any`); } /** @@ -22,14 +24,14 @@ export class Arg { * @param match The condition used to match the value. */ public static is(match: (value: T) => boolean): T & Arg { - return new Arg(value => ({ valid: match(value) }), `is`); + return new Arg(match, `is`); } /** * Allows only a null value. */ public static null(): T & Arg { - return new Arg(value => ({ valid: value === null }), `null`); + return new Arg(value => value === null, `null`); } /** @@ -43,7 +45,7 @@ export class Arg { * Allows only an undefined value. */ public static undefined(): T & Arg { - return new Arg(value => ({ valid: value === undefined }), `undefined`); + return new Arg(value => value === undefined, `undefined`); } /** @@ -67,20 +69,24 @@ export class 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 => ({ valid: min <= value && value <= max }), `between ${min} and ${max}`); + 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 => ({ valid: values.indexOf(value) > -1 }), `in ${values.join(", ")}`); + return new Arg(value => values.indexOf(value) > -1, `in ${values.join(", ")}`); } /** @@ -94,19 +100,26 @@ export class Arg { * Allows any value that matches the provided pattern. */ public static match(pattern: RegExp): T & Arg { - return new Arg(value => ({ valid: pattern.test(value) }), `matches ${pattern}`); + return new Arg(value => pattern.test(value), `matches ${pattern}`); } public static startsWith(text: string): string & Arg { - return new Arg(value => ({ valid: String(value).startsWith(text) }), `starts with ${text}`); + return new Arg(value => typeof value === "string" && value.startsWith(text), `starts with ${text}`); } public static endsWith(text: string): string & Arg { - return new Arg(value => ({ valid: String(value).endsWith(text) }), `ends with ${text}`); + return new Arg(value => typeof value === "string" && value.endsWith(text), `ends with ${text}`); } - public static includes(text: string): string & Arg { - return new Arg(value => ({ valid: String(value).includes(text) }), `contains ${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_.includes(value) : 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(", ")}]`); } /** @@ -142,7 +155,7 @@ export class Arg { */ public static typeof(tag: string): T & Arg; public static typeof(tag: string): any { - return new Arg(value => ({ valid: typeof value === tag }), `typeof ${tag}`); + return new Arg(value => typeof value === tag, `typeof ${tag}`); } public static string() { return this.typeof("string"); } @@ -157,21 +170,21 @@ export class Arg { * @param type The expected constructor. */ public static instanceof(type: TClass): TClass["prototype"] & Arg { - return new Arg(value => ({ valid: value instanceof type }), `instanceof ${type.name}`); + 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 => ({ valid: names.filter(name => name in value).length === names.length }), `has ${names.join(", ")}`); + 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 => ({ valid: names.filter(name => Object.prototype.hasOwnProperty.call(value, name)).length === names.length }), `hasOwn ${names.join(", ")}`); + return new Arg(value => names.filter(name => Object.prototype.hasOwnProperty.call(value, name)).length === names.length, `hasOwn ${names.join(", ")}`); } /** @@ -180,21 +193,11 @@ export class Arg { */ public static rest(condition?: T | (T & Arg)): T & Arg { if (condition === undefined) { - return new Arg((_, args) => ({ valid: true, next: args.length }), `rest`); + return new Arg(() => true, `rest`, /*rest*/ true); } const arg = Arg.from(condition); - return new Arg( - (_, args, index) => { - while (index < args.length) { - const { valid, next } = Arg.validate(arg, args, index); - if (!valid) return { valid: false }; - index = typeof next === "undefined" ? index + 1 : next; - } - return { valid: true, next: index }; - }, - `rest ${arg._message}` - ); + return new Arg(value => arg._validate(value), `rest ${arg._message}`, /*rest*/ true); } /** @@ -202,10 +205,7 @@ export class Arg { */ public static not(value: T | (T & Arg)): T & Arg { const arg = Arg.from(value); - return new Arg((value, args, index) => { - const result = arg._condition(value, args, index); - return { valid: !result.valid, next: result.next }; - }, `not ${arg._message}`); + return new Arg(value => !arg._validate(value), `not ${arg._message}`); } /** @@ -213,20 +213,15 @@ export class Arg { */ public static and(...args: ((T & Arg) | T)[]): T & Arg { const conditions = args.map(Arg.from); - return new Arg((value, args, index) => { - for (const condition of conditions) { - const result = condition._condition(value, args, index); - if (!result.valid) return { valid: false }; - } - return { valid: true }; - }, conditions.map(condition => condition._message).join(" and ")); + return new Arg(value => conditions.every(condition => condition._validate(value)), conditions.map(condition => condition._message).join(" and ")); } /** * Combines conditions, where no condition may be `true`. */ public static nand(...args: ((T & Arg) | T)[]): T & Arg { - return this.not(this.and(...args)); + const conditions = args.map(Arg.from); + return new Arg(value => !conditions.every(condition => condition._validate(value)), "not " + conditions.map(condition => condition._message).join(" and ")); } /** @@ -234,20 +229,15 @@ export class Arg { */ public static or(...args: ((T & Arg) | T)[]): T & Arg { const conditions = args.map(Arg.from); - return new Arg((value, args, index) => { - for (const condition of conditions) { - const result = condition._condition(value, args, index); - if (result.valid) return { valid: true }; - } - return { valid: false }; - }, conditions.map(condition => condition._message).join(" or ")); + return new Arg(value => conditions.some(condition => condition._validate(value)), conditions.map(condition => condition._message).join(" or ")); } /** * Combines conditions, where all conditions must be `true`. */ public static nor(...args: ((T & Arg) | T)[]): T & Arg { - return this.not(this.or(...args)); + const conditions = args.map(Arg.from); + return new Arg(value => !conditions.some(condition => condition._validate(value)), "neither " + conditions.map(condition => condition._message).join(" nor ")); } /** @@ -256,25 +246,40 @@ export class Arg { * @returns The condition */ public static from(value: T): T & Arg { - if (value instanceof Arg) { - return value; - } + return value instanceof Arg ? value : + value === undefined ? Arg.undefined() : + value === null ? Arg.null() : + new Arg(v => is(v, value), JSON.stringify(value)); + } - return new Arg(v => ({ valid: 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 args The arguments for the execution - * @param index The current index into the `args` array - * @returns An object that specifies whether the condition is `valid` and what the `next` index should be. + * @param conditions The conditions to validate. + * @param args The arguments for the execution. */ - public static validate(arg: Arg, args: ReadonlyArray, index: number): { valid: boolean, next?: number } { - const value = index >= 0 && index < args.length ? args[index] : undefined; - const { valid, next } = arg._condition(value, args, index); - return valid - ? { valid: true, next: next === undefined ? index + 1 : next } - : { valid: false }; + 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; } /** diff --git a/scripts/typemock/src/index.ts b/scripts/typemock/src/index.ts index aa94febd01b..1626ee04e4b 100644 --- a/scripts/typemock/src/index.ts +++ b/scripts/typemock/src/index.ts @@ -1,6 +1,25 @@ export { Arg } from "./arg"; export { Times } from "./times"; -export { Mock, Returns, Throws } from "./mock"; -export { Spy, Callable, Constructable } from "./spy"; -export { Stub } from "./stub"; -export { Timers, Timer, Timeout, Interval, Immediate, AnimationFrame } from "./timers"; \ No newline at end of file +export { Mock, Spy, Returns, Throws, ThisArg, Callback, Fallback, Setup, Callable, Constructable } from "./mock"; +export { Inject } from "./inject"; +export { Timers, Timer, Timeout, Interval, Immediate, AnimationFrame } from "./timers"; + +import { Mock, Spy, Callable, Constructable } from "./mock"; + +/** + * 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); +} diff --git a/scripts/typemock/src/stub.ts b/scripts/typemock/src/inject.ts similarity index 83% rename from scripts/typemock/src/stub.ts rename to scripts/typemock/src/inject.ts index e643aae44d6..d7b27fe8290 100644 --- a/scripts/typemock/src/stub.ts +++ b/scripts/typemock/src/inject.ts @@ -1,7 +1,7 @@ /** * Temporarily injects a value into an object property */ -export class Stub { +export class Inject { private _target: T; private _key: K; private _value: any; @@ -28,11 +28,11 @@ export class Stub { return this._key; } - public get stubValue(): T[K] { + public get injectedValue(): T[K] { return this._installed ? this.currentValue : this._value; } - public set stubValue(value: T[K]) { + public set injectedValue(value: T[K]) { if (this._installed) { this._target[this._key] = value; } @@ -79,8 +79,8 @@ export class Stub { this._originalValue = null; } - public static exec(target: T, propertyKey: K, value: T[K], action: () => V) { - const stub = new Stub(target, propertyKey, value); + public static exec(target: T, propertyKey: K, value: T[K], action: () => V) { + const stub = new Inject(target, propertyKey, value); return stub.exec(action); } diff --git a/scripts/typemock/src/mock.ts b/scripts/typemock/src/mock.ts index 85d441c8382..b4dfd9e0af9 100644 --- a/scripts/typemock/src/mock.ts +++ b/scripts/typemock/src/mock.ts @@ -1,28 +1,53 @@ import { Times } from "./times"; import { Arg } from "./arg"; +import { Inject } from "./inject"; const weakHandler = new WeakMap>(); +const weakMock = new WeakMap>(); function noop() {} +const empty = {}; -function getHandler(value: object) { - return weakHandler.get(value); +export type Callable = (...args: any[]) => any; + +export type Constructable = new (...args: any[]) => any; + +export interface ThisArg { + this: any; } export interface Returns { - returns: U; + return: U; +} + +export interface Fallback { + fallback: true; } export interface Throws { - throws: any; + 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 _target: T; - private _handler = new MockHandler(); + private _handler: MockHandler; private _proxy: T; private _revoke: () => void; @@ -32,13 +57,16 @@ export class Mock { * @param setups Optional setups to use */ constructor(target: T = {}, setups?: Partial) { - this._target = target; + this._handler = typeof target === "function" + ? new MockFunctionHandler() + : new MockHandler(); - const { proxy, revoke } = Proxy.revocable(this._target, this._handler); + 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); @@ -48,24 +76,66 @@ export class Mock { /** * Gets the mock version of the target */ - public get value(): T { + 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() { + return new Mock(function () {}); + } + + /** + * 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?: Returns | Throws): Mock; + 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): Mock; - public setup(setup: Partial | ((value: T) => U), result?: Returns | Throws): Mock { + public setup(setups: Partial): this; + public setup(setup: Partial | ((value: T) => U), result?: Setup): this { if (typeof setup === "function") { this._handler.setupCall(setup, result); } @@ -75,187 +145,259 @@ export class Mock { 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) => any, times: Times): Mock { + 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); return this; } public revoke() { + weakMock.delete(this._proxy); + weakHandler.delete(this._proxy); this._handler.revoke(); this._revoke(); } } -class Setup { - public recording: Recording; - public result: Partial & Throws> | undefined; +export class Spy any }, K extends keyof T> extends Mock { + private _spy: Inject | undefined; - constructor (recording: Recording, result?: Returns | Throws) { - this.recording = recording; - this.result = result; + constructor(target: T, propertyKey: K) { + super(target[propertyKey]); + this._spy = new Inject(target, propertyKey, this.proxy); + this._spy.install(); } - public static evaluate(setups: ReadonlyArray | undefined, trap: string, args: any[], newTarget?: any) { - if (setups) { - for (let i = setups.length - 1; i >= 0; i--) { - const setup = setups[i]; - if (setup.recording.trap === trap && - setup.recording.newTarget === newTarget && - setup.matchArguments(args)) { - return setup.getResult(); - } - } + 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; } - throw new Error("No matching setups."); - } - - public matchArguments(args: any[]) { - return this.recording.matchArguments(args); - } - - public getResult() { - if (this.result) { - if (this.result.throws) { - throw this.result.throws; - } - return this.result.returns; - } - return undefined; + super.revoke(); } } class Recording { + public static readonly noThisArg = {}; public readonly trap: string; public readonly name: PropertyKey | undefined; - public readonly args: ReadonlyArray; + 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, args: ReadonlyArray, newTarget?: any) { + 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.args = args || []; + this.thisArg = thisArg; + this.argArray = argArray || []; this.newTarget = newTarget; + this.result = result; + this.callback = callback; } - public get conditions() { - return this._conditions || (this._conditions = this.args.map(Arg.from)); + 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.conditions.join(", ")})${this.newTarget ? ` [${this.newTarget.name}]` : ``}`; + 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 + && Arg.validate(this.thisCondition, thisArg) + && Arg.validateAll(this.argConditions, argArray) + && Arg.validate(this.newTargetCondition, newTarget); } public matchRecording(recording: Recording) { - if (recording.trap !== this.trap || - recording.name !== this.name || - recording.newTarget !== this.newTarget) { - return false; - } - - return this.matchArguments(recording.args); + return this.match(recording.trap, recording.name, recording.thisArg, recording.argArray, recording.newTarget) + && this.matchResult(recording.result); } - public matchArguments(args: ReadonlyArray) { - let argi = 0; - while (argi < this.conditions.length) { - const condition = this.conditions[argi]; - const { valid, next } = Arg.validate(condition, args, argi); - if (!valid) { - return false; - } - argi = typeof next === "number" ? next : argi + 1; - } - if (argi < args.length) { - return false; - } - return true; + 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 { - private readonly overrides = Object.create(null); - private readonly recordings: Recording[] = []; - private readonly selfSetups: Setup[] = []; - private readonly memberSetups = new Map(); - private readonly methodTargets = new WeakMap(); - private readonly methodProxies = new Map(); - private readonly methodRevocations = new Set<() => void>(); + protected readonly overrides = Object.create(null); + protected readonly recordings: Recording[] = []; + protected readonly setups: Recording[] = []; + protected readonly methodTargets = new WeakMap(); + protected readonly methodProxies = new Map(); + protected readonly methodRevocations = new Set<() => void>(); - constructor() { - } - - public apply(target: T | Function, thisArg: any, argArray: any[]): any { - if (typeof target === "function") { - this.recordings.push(new Recording("apply", undefined, argArray)); - return this.selfSetups.length > 0 - ? Setup.evaluate(this.selfSetups, "apply", argArray) - : Reflect.apply(target, thisArg, argArray); + 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; } - return undefined; - } - - public construct(target: T | Function, argArray: any[], newTarget?: any): any { - if (typeof target === "function") { - this.recordings.push(new Recording("construct", undefined, argArray, newTarget)); - return this.selfSetups.length > 0 - ? Setup.evaluate(this.selfSetups, "construct", argArray, newTarget) - : Reflect.construct(target, argArray, newTarget); + catch (e) { + throw result.throw = e; } - return undefined; } - public get(target: T, name: PropertyKey, receiver: any): any { - this.recordings.push(new Recording("get", name, [])); - const value = Reflect.get(this.getTarget(target, name), name, receiver); - return typeof value === "function" ? this.getMethod(name, value) : value; - } - - public set(target: T, name: PropertyKey, value: any, receiver: any): boolean { - this.recordings.push(new Recording("set", name, [value])); + 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); } - return Reflect.set(this.getTarget(target, name), name, value, receiver); + 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 { - this.recordings.push(new Recording("invoke", name, argArray)); - return Reflect.apply(method, proxy, argArray); + 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: Returns | Throws | undefined) { - const recording = capture(callback); - if (recording.name === undefined) { - this.selfSetups.push(new Setup(recording, result)); + 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 { - let setups = this.memberSetups.get(recording.name); - if (!setups) { - this.memberSetups.set(recording.name, setups = []); - if (recording.trap === "invoke") { - this.defineMethod(recording.name); - } - else { - this.defineAccessor(recording.name); - } + else if (recording.name !== undefined) { + if (recording.kind === "method") { + this.defineMethod(recording.name); } - else { - if ((setups[0].recording.trap === "invoke") !== (recording.trap === "invoke")) { - throw new Error(`Cannot mix method and acessor setups for the same property.`); - } + else if (recording.kind === "property") { + this.defineAccessor(recording.name); } + } - setups.push(new Setup(recording, result)); - } + this.setups.push(recording); } public setupMembers(setup: object) { @@ -270,8 +412,8 @@ class MockHandler implements ProxyHandler { } } - public verify(callback: (value: T) => any, times: Times): void { - const expectation = capture(callback); + public verify(callback: (value: T) => U, times: Times, message?: string): void { + const expectation = this.capture(callback, /*result*/ undefined); let count: number = 0; for (const recording of this.recordings) { @@ -280,7 +422,7 @@ class MockHandler implements ProxyHandler { } } - times.check(count, `An error occured when verifying expectation: ${expectation}`); + times.check(count, message || `An error occured when verifying expectation: ${expectation}`); } public getTarget(target: T, name: PropertyKey) { @@ -307,28 +449,80 @@ class MockHandler implements ProxyHandler { } } + 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.memberSetups; + const setups = this.setups; this.setupMembers({ - [name](...args: any[]) { - return Setup.evaluate(setups.get(name), "invoke", args); + [name](...argArray: any[]) { + return Recording.evaluate(setups, "invoke", name, this, argArray, /*newTarget*/ undefined, noop); } }); } private defineAccessor(name: PropertyKey) { - const setups = this.memberSetups; + const setups = this.setups; this.setupMembers({ get [name]() { - return Setup.evaluate(setups.get(name), "get", []); + return Recording.evaluate(setups, "get", name, this, [], /*newTarget*/ undefined, noop); }, set [name](value: any) { - Setup.evaluate(setups.get(name), "set", [value]); + 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; @@ -337,58 +531,53 @@ class MethodHandler { } public apply(target: Function, thisArgument: any, argumentsList: any[]): any { - const handler = getHandler(thisArgument); + const handler = weakHandler.get(thisArgument); return handler ? handler.invoke(thisArgument, this.name, target, argumentsList) : Reflect.apply(target, thisArgument, argumentsList); } } -class CapturingHandler { +class CapturingHandler implements ProxyHandler { public recording: Recording | undefined; - private _name: PropertyKey; - private _method: Function; + protected readonly callback: Callable | undefined; + protected readonly thisArg: any; + protected readonly result: Returns | Throws | Fallback | undefined; - constructor() { - this._method = (...args: any[]) => { - this.recording = new Recording("invoke", this._name, args); - }; + 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 apply(_target: object, _thisArg: any, argArray: any[]): any { - this.recording = new Recording("apply", /*name*/ undefined, argArray); - return 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 construct(_target: object, argArray: any[], newTarget?: any): any { - this.recording = new Recording("construct", /*name*/ undefined, argArray, newTarget); - return undefined; - } - - public get(_target: object, name: PropertyKey, _receiver: any): any { - this.recording = new Recording("get", name, []); - this._name = name; - return this._method; - } - - public set(_target: object, name: PropertyKey, value: any, _receiver: any): boolean { - this.recording = new Recording("set", name, [value]); + 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; } } -function capture(callback: (value: T) => U): Recording { - const handler = new CapturingHandler(); - const { proxy, revoke } = Proxy.revocable(noop, handler); - try { - callback(proxy); - if (!handler.recording) { - throw new Error("Nothing was captured."); - } - return handler.recording; +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; } - finally { - revoke(); + + 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); } \ No newline at end of file diff --git a/scripts/typemock/src/spy.ts b/scripts/typemock/src/spy.ts deleted file mode 100644 index 2bb21e6e21d..00000000000 --- a/scripts/typemock/src/spy.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Mock } from "./mock"; -import { Times } from "./times"; -import { Arg } from "./arg"; - -function noop() {} - -export type Callable = ((...args: any[]) => any); - -export type Constructable = (new (...args: any[]) => any); - -export class Spy { - private _mock: Mock; - - constructor(target = noop) { - this._mock = new Mock(target); - } - - public get value(): T { - return this._mock.value; - } - - public verify(callback: (value: T) => any, times: Times): this { - this._mock.verify(callback, times); - return this; - } - - public called(times: Times): this { - return this.verify(_ => (_)(Arg.rest()), times); - } - - public constructed(times: Times): this { - return this.verify(_ => new (_)(Arg.rest()), times); - } - - public revoke(): void { - this._mock.revoke(); - } -} diff --git a/scripts/typemock/src/tests/argTests.ts b/scripts/typemock/src/tests/argTests.ts index 1203097ec5c..03d96f1c469 100644 --- a/scripts/typemock/src/tests/argTests.ts +++ b/scripts/typemock/src/tests/argTests.ts @@ -9,11 +9,10 @@ describe("arg", () => { const target = Arg.from(Arg.any()); // act - const result = Arg.validate(target, ["a"], 0); + const result = Arg.validate(target, "a"); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("toString", () => { // arrange @@ -32,21 +31,20 @@ describe("arg", () => { const target = Arg.from(Arg.is(value => value === "a")); // act - const result = Arg.validate(target, ["a"], 0); + const result = Arg.validate(target, "a"); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.is(value => value === "a")); // act - const result = Arg.validate(target, ["b"], 0); + const result = Arg.validate(target, "b"); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -65,21 +63,20 @@ describe("arg", () => { const target = Arg.from(Arg.notNull()); // act - const result = Arg.validate(target, [{}], 0); + const result = Arg.validate(target, {}); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.notNull()); // act - const result = Arg.validate(target, [null], 0); + const result = Arg.validate(target, null); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -98,21 +95,20 @@ describe("arg", () => { const target = Arg.from(Arg.null()); // act - const result = Arg.validate(target, [null], 0); + const result = Arg.validate(target, null); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.null()); // act - const result = Arg.validate(target, [{}], 0); + const result = Arg.validate(target, {}); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -131,21 +127,20 @@ describe("arg", () => { const target = Arg.from(Arg.notUndefined()); // act - const result = Arg.validate(target, [{}], 0); + const result = Arg.validate(target, {}); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.notUndefined()); // act - const result = Arg.validate(target, [undefined], 0); + const result = Arg.validate(target, undefined); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -164,21 +159,20 @@ describe("arg", () => { const target = Arg.from(Arg.undefined()); // act - const result = Arg.validate(target, [undefined], 0); + const result = Arg.validate(target, undefined); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.undefined()); // act - const result = Arg.validate(target, [{}], 0); + const result = Arg.validate(target, {}); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -197,31 +191,30 @@ describe("arg", () => { const target = Arg.from(Arg.notNullOrUndefined()); // act - const result = Arg.validate(target, [{}], 0); + const result = Arg.validate(target, {}); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("invalid (null)", () => { // arrange const target = Arg.from(Arg.notNullOrUndefined()); // act - const result = Arg.validate(target, [null], 0); + const result = Arg.validate(target, null); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("invalid (undefined)", () => { // arrange const target = Arg.from(Arg.notNullOrUndefined()); // act - const result = Arg.validate(target, [undefined], 0); + const result = Arg.validate(target, undefined); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -240,32 +233,30 @@ describe("arg", () => { const target = Arg.from(Arg.nullOrUndefined()); // act - const result = Arg.validate(target, [null], 0); + const result = Arg.validate(target, null); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("valid (undefined)", () => { // arrange const target = Arg.from(Arg.nullOrUndefined()); // act - const result = Arg.validate(target, [undefined], 0); + const result = Arg.validate(target, undefined); // assert - assert.isTrue(result.valid); - assert.strictEqual(result.next, 1); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.nullOrUndefined()); // act - const result = Arg.validate(target, [{}], 0); + const result = Arg.validate(target, {}); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -284,26 +275,26 @@ describe("arg", () => { const target = Arg.from(Arg.between(1, 3)); // act - const min = Arg.validate(target, [1], 0); - const mid = Arg.validate(target, [2], 0); - const max = Arg.validate(target, [3], 0); + const min = Arg.validate(target, 1); + const mid = Arg.validate(target, 2); + const max = Arg.validate(target, 3); // assert - assert.isTrue(min.valid); - assert.isTrue(mid.valid); - assert.isTrue(max.valid); + assert.isTrue(min); + assert.isTrue(mid); + assert.isTrue(max); }); it("invalid", () => { // arrange const target = Arg.from(Arg.between(1, 3)); // act - const before = Arg.validate(target, [0], 0); - const after = Arg.validate(target, [4], 0); + const before = Arg.validate(target, 0); + const after = Arg.validate(target, 4); // assert - assert.isFalse(before.valid); - assert.isFalse(after.valid); + assert.isFalse(before); + assert.isFalse(after); }); it("toString", () => { // arrange @@ -322,20 +313,20 @@ describe("arg", () => { const target = Arg.from(Arg.in(["a", "b"])); // act - const result = Arg.validate(target, ["a"], 0); + const result = Arg.validate(target, "a"); // assert - assert.isTrue(result.valid); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.in(["a", "b"])); // act - const result = Arg.validate(target, ["c"], 0); + const result = Arg.validate(target, "c"); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -354,20 +345,20 @@ describe("arg", () => { const target = Arg.from(Arg.notIn(["a", "b"])); // act - const result = Arg.validate(target, ["c"], 0); + const result = Arg.validate(target, "c"); // assert - assert.isTrue(result.valid); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.notIn(["a", "b"])); // act - const result = Arg.validate(target, ["a"], 0); + const result = Arg.validate(target, "a"); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -386,20 +377,20 @@ describe("arg", () => { const target = Arg.from(Arg.match(/^a$/)); // act - const result = Arg.validate(target, ["a"], 0); + const result = Arg.validate(target, "a"); // assert - assert.isTrue(result.valid); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.match(/^a$/)); // act - const result = Arg.validate(target, ["b"], 0); + const result = Arg.validate(target, "b"); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -418,20 +409,20 @@ describe("arg", () => { const target = Arg.from(Arg.typeof("number")); // act - const result = Arg.validate(target, [1], 0); + const result = Arg.validate(target, 1); // assert - assert.isTrue(result.valid); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from(Arg.typeof("number")); // act - const result = Arg.validate(target, ["a"], 0); + const result = Arg.validate(target, "a"); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -451,10 +442,10 @@ describe("arg", () => { const target = Arg.from(Arg.instanceof(C)); // act - const result = Arg.validate(target, [new C()], 0); + const result = Arg.validate(target, new C()); // assert - assert.isTrue(result.valid); + assert.isTrue(result); }); it("invalid", () => { // arrange @@ -462,10 +453,10 @@ describe("arg", () => { const target = Arg.from(Arg.instanceof(C)); // act - const result = Arg.validate(target, [{}], 0); + const result = Arg.validate(target, {}); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -485,22 +476,22 @@ describe("arg", () => { const target = Arg.from(Arg.has("a")); // act - const own = Arg.validate(target, [{ a: 1 }], 0); - const proto = Arg.validate(target, [{ __proto__: { a: 1 } }], 0); + const own = Arg.validate(target, { a: 1 }); + const proto = Arg.validate(target, { __proto__: { a: 1 } }); // assert - assert.isTrue(own.valid); - assert.isTrue(proto.valid); + assert.isTrue(own); + assert.isTrue(proto); }); it("invalid", () => { // arrange const target = Arg.from(Arg.has("a")); // act - const result = Arg.validate(target, [{ b: 1 }], 0); + const result = Arg.validate(target, { b: 1 }); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -519,22 +510,22 @@ describe("arg", () => { const target = Arg.from(Arg.hasOwn("a")); // act - const own = Arg.validate(target, [{ a: 1 }], 0); + const own = Arg.validate(target, { a: 1 }); // assert - assert.isTrue(own.valid); + assert.isTrue(own); }); it("invalid", () => { // arrange const target = Arg.from(Arg.hasOwn("a")); // act - const result = Arg.validate(target, [{ b: 1 }], 0); - const proto = Arg.validate(target, [{ __proto__: { a: 1 } }], 0); + const result = Arg.validate(target, { b: 1 }); + const proto = Arg.validate(target, { __proto__: { a: 1 } }); // assert - assert.isFalse(result.valid); - assert.isFalse(proto.valid); + assert.isFalse(result); + assert.isFalse(proto); }); it("toString", () => { // arrange @@ -554,14 +545,12 @@ describe("arg", () => { const target = Arg.from(Arg.rest()); // act - const empty = Arg.validate(target, [], 0); - const multiple = Arg.validate(target, ["a", "b"], 0); + const empty = Arg.validateAll([target], []); + const multiple = Arg.validateAll([target], ["a", "b"]); // assert - assert.isTrue(empty.valid); - assert.strictEqual(empty.next, 0); - assert.isTrue(multiple.valid); - assert.strictEqual(multiple.next, 2); + assert.isTrue(empty); + assert.isTrue(multiple); }); it("toString", () => { // arrange @@ -580,24 +569,22 @@ describe("arg", () => { const target = Arg.from(Arg.rest(Arg.typeof("string"))); // act - const empty = Arg.validate(target, [], 0); - const multiple = Arg.validate(target, ["a", "b"], 0); + const empty = Arg.validateAll([target], []); + const multiple = Arg.validateAll([target], ["a", "b"]); // assert - assert.isTrue(empty.valid); - assert.strictEqual(empty.next, 0); - assert.isTrue(multiple.valid); - assert.strictEqual(multiple.next, 2); + assert.isTrue(empty); + assert.isTrue(multiple); }); it("invalid", () => { // arrange const target = Arg.from(Arg.rest(Arg.typeof("string"))); // act - const result = Arg.validate(target, ["a", 1], 0); + const result = Arg.validateAll([target], ["a", 1]); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange @@ -617,20 +604,20 @@ describe("arg", () => { const target = Arg.from("a"); // act - const result = Arg.validate(target, ["a"], 0); + const result = Arg.validate(target, "a"); // assert - assert.isTrue(result.valid); + assert.isTrue(result); }); it("invalid", () => { // arrange const target = Arg.from("a"); // act - const result = Arg.validate(target, ["b"], 0); + const result = Arg.validate(target, "b"); // assert - assert.isFalse(result.valid); + assert.isFalse(result); }); it("toString", () => { // arrange diff --git a/scripts/typemock/src/tests/index.ts b/scripts/typemock/src/tests/index.ts index 45a2246e286..1ff30bfad40 100644 --- a/scripts/typemock/src/tests/index.ts +++ b/scripts/typemock/src/tests/index.ts @@ -1,5 +1,5 @@ import "./argTests"; import "./timesTests"; import "./mockTests"; -import "./stubTests"; +import "./injectTests"; import "./timersTests"; \ No newline at end of file diff --git a/scripts/typemock/src/tests/injectTests.ts b/scripts/typemock/src/tests/injectTests.ts new file mode 100644 index 00000000000..c7aa747e314 --- /dev/null +++ b/scripts/typemock/src/tests/injectTests.ts @@ -0,0 +1,79 @@ +import "./sourceMapSupport"; +import { Mock } from "../mock"; +import { Inject } from "../inject"; +import { Times } from "../times"; +import { assert } from "chai"; + +describe("inject", () => { + it("install replaces value", () => { + // arrange + const mock = new Mock({ a: 1 }); + const inject = new Inject(mock.proxy, "a", 2); + + // act + inject.install(); + + // assert + mock.verify(_ => _.a = 2, Times.once()); + }); + it("install is installed", () => { + // arrange + const mock = new Mock({ a: 1 }); + const inject = new Inject(mock.proxy, "a", 2); + + // act + inject.install(); + + // assert + assert.isTrue(inject.installed); + }); + it("install twice only installs once", () => { + // arrange + const mock = new Mock({ a: 1 }); + const inject = new Inject(mock.proxy, "a", 2); + + // act + inject.install(); + inject.install(); + + // assert + mock.verify(_ => _.a = 2, Times.once()); + }); + it("uninstall restores value", () => { + // arrange + const mock = new Mock({ a: 1 }); + const inject = new Inject(mock.proxy, "a", 2); + inject.install(); + + // act + inject.uninstall(); + + // assert + mock.verify(_ => _.a = 1, Times.once()); + }); + it("uninstall is not installed", () => { + // arrange + const mock = new Mock({ a: 1 }); + const inject = new Inject(mock.proxy, "a", 2); + inject.install(); + + // act + inject.uninstall(); + + // assert + assert.isFalse(inject.installed); + }); + it("uninstall twice only uninstalls once", () => { + // arrange + const mock = new Mock({ a: 1 }); + const inject = new Inject(mock.proxy, "a", 2); + inject.install(); + + // act + inject.uninstall(); + inject.uninstall(); + + // assert + mock.verify(_ => _.a = 1, Times.once()); + }); +}); diff --git a/scripts/typemock/src/tests/mockTests.ts b/scripts/typemock/src/tests/mockTests.ts index 38efbdb04c7..f461c289c85 100644 --- a/scripts/typemock/src/tests/mockTests.ts +++ b/scripts/typemock/src/tests/mockTests.ts @@ -1,262 +1,363 @@ import "./sourceMapSupport"; import { Mock } from "../mock"; -import { Stub } from "../stub"; +import { Inject } from "../inject"; import { Arg } from "../arg"; import { Times } from "../times"; import { recordError } from "./utils"; import { assert } from "chai"; describe("mock", () => { - it("mock get with no setups", () => { - // arrange - const target = { a: 1 }; - const mock = new Mock(target); + describe("no setup", () => { + it("get (exists)", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target); - // act - const result = mock.value.a; + // act + const result = mock.proxy.a; - // assert - assert.equal(1, result); - }); - it("mock setup property get with return", () => { - // arrange - const target = { a: 1 }; - const mock = new Mock(target, { get a() { return 2; } }); - - // act - const result = mock.value.a; - - // assert - assert.equal(2, result); - }); - it("mock setup property get with throw", () => { - // arrange - const target = { a: 1 }; - const error = new Error("error"); - const mock = new Mock(target, { get a(): number { throw error; } }); - - // act - const e = recordError(() => mock.value.a); - - // assert - assert.strictEqual(error, e); - }); - it("mock setup property set", () => { - // arrange - let _a: number | undefined; - const target = { a: 1 }; - const mock = new Mock(target, { set a(value: number) { _a = value; } }); - - // act - mock.value.a = 2; - - // assert - assert.equal(2, _a); - assert.equal(1, target.a); - }); - it("mock setup property set with throw", () => { - // arrange - const target = { a: 1 }; - const error = new Error("error"); - const mock = new Mock(target, { set a(value: number) { throw error; } }); - - // act - const e = recordError(() => mock.value.a = 2); - - // assert - assert.strictEqual(error, e); - }); - it("mock setup method call no setups", () => { - // arrange - const target = { a() { return 1; } }; - const mock = new Mock(target); - - // act - const result = mock.value.a(); - - // assert - assert.equal(1, result); - }); - it("mock setup method callback", () => { - // arrange - const target = { a() { return 1; } }; - const mock = new Mock(target, { a() { return 2; } }); - - // act - const result = mock.value.a(); - - // assert - assert.equal(2, result); - }); - it("mock setup method callback throws", () => { - // arrange - const target = { a() { return 1; } }; - const error = new Error("error"); - const mock = new Mock(target, { a(): number { throw error; } }); - - // act - const e = recordError(() => mock.value.a()); - - // assert - assert.strictEqual(error, e); - }); - it("mock setup new property", () => { - // arrange - const target = { a: 1 }; - const mock = new Mock(target, { b: 2 }); - - // act - const result = (mock.value).b; - - // assert - assert.equal(2, result); - }); - it("mock setup new method", () => { - // arrange - const target = { a: 1 }; - const mock = new Mock(target, { b() { return 2; } }); - - // act - const result = (mock.value).b(); - - // assert - assert.equal(2, result); - }); - it("mock verify get no setups, not called throws", () => { - // arrange - const target = { a: 1 }; - const mock = new Mock(target); - - // act - const e = recordError(() => mock.verify(_ => _.a, Times.once())); - - // assert - assert.instanceOf(e, Error); - }); - it("mock verify get no setups, called passes", () => { - // arrange - const target = { a: 1 }; - const mock = new Mock(target); - const result = mock.value.a; - - // act - const e = recordError(() => mock.verify(_ => _.a, Times.once())); - - // assert - assert.isUndefined(e); - }); - it("mock verify setup get, called passes", () => { - // arrange - const target = { a: 1 }; - const mock = new Mock(target, { get a() { return 2 } }); - const result = mock.value.a; - - // act - const e = recordError(() => mock.verify(_ => _.a, Times.once())); - - // assert - assert.isUndefined(e); - }); - it("mock verify method no setups, not called throws", () => { - // arrange - const target = { a() { return 1; } }; - const mock = new Mock(target); - - // act - const e = recordError(() => mock.verify(_ => _.a(), Times.once())); - - // assert - assert.instanceOf(e, Error); - }); - it("mock verify method no setups, called passes", () => { - // arrange - const target = { a() { return 1; } }; - const mock = new Mock(target); - const result = mock.value.a(); - - // act - const e = recordError(() => mock.verify(_ => _.a(), Times.once())); - - // assert - assert.isUndefined(e); - }); - it("mock verify setup method, called passes", () => { - // arrange - const target = { a(x: number) { return x + 1; } }; - const mock = new Mock(target, { - a(x: number) { - return x + 2; - } + // assert + assert.equal(result, 1); }); - const result = mock.value.a(3); + it("get (missing)", () => { + // arrange + const mock = new Mock<{ a?: number }>(); - // act - const e = recordError(() => mock.verify(_ => _.a(Arg.number()), Times.once())); + // act + const result = mock.proxy.a; - // assert - assert.isUndefined(e); + // assert + assert.isUndefined(result); + }); + it("set (exists)", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target); + + // act + mock.proxy.a = 2; + const result = mock.proxy.a; + + // assert + assert.equal(result, 2); + }); + it("set (missing)", () => { + // arrange + const mock = new Mock<{ a?: number }>(); + + // act + mock.proxy.a = 2; + const result = mock.proxy.a; + + // assert + assert.equal(result, 2); + }); + it("method (existing)", () => { + // arrange + const target = { a() { return 1; } }; + const mock = new Mock(target); + + // act + const result = mock.proxy.a(); + + // assert + assert.equal(1, result); + }); }); - it("mock setup method using callback", () => { - // arrange - const mock = new Mock<{ a(x: number): number; }>(); - mock.setup(_ => _.a(1), { returns: 2 }); - // act - const result = mock.value.a(1); + describe("setup", () => { + describe("using object", () => { + it("get", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target, { get a() { return 2; } }); - // assert - assert.strictEqual(result, 2); + // act + const result = mock.proxy.a; + + // assert + assert.equal(2, result); + }); + it("get with throw", () => { + // arrange + const target = { a: 1 }; + const error = new Error("error"); + const mock = new Mock(target, { get a(): number { throw error; } }); + + // act + const e = recordError(() => mock.proxy.a); + + // assert + assert.strictEqual(error, e); + }); + it("set", () => { + // arrange + let _a: number | undefined; + const target = { a: 1 }; + const mock = new Mock(target, { set a(value: number) { _a = value; } }); + + // act + mock.proxy.a = 2; + + // assert + assert.equal(2, _a); + assert.equal(1, target.a); + }); + it("set with throw", () => { + // arrange + const target = { a: 1 }; + const error = new Error("error"); + const mock = new Mock(target, { set a(value: number) { throw error; } }); + + // act + const e = recordError(() => mock.proxy.a = 2); + + // assert + assert.strictEqual(error, e); + }); + it("method", () => { + // arrange + const target = { a() { return 1; } }; + const mock = new Mock(target, { a() { return 2; } }); + + // act + const result = mock.proxy.a(); + + // assert + assert.equal(2, result); + }); + it("method throws", () => { + // arrange + const target = { a() { return 1; } }; + const error = new Error("error"); + const mock = new Mock(target, { a(): number { throw error; } }); + + // act + const e = recordError(() => mock.proxy.a()); + + // assert + assert.strictEqual(error, e); + }); + it("new property", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target, { b: 2 }); + + // act + const result = (mock.proxy).b; + + // assert + assert.equal(2, result); + }); + it("new method", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target, { b() { return 2; } }); + + // act + const result = (mock.proxy).b(); + + // assert + assert.equal(2, result); + }); + }); + describe("using callback", () => { + it("get only", () => { + // arrange + const mock = new Mock<{ a: number }>(); + mock.setup(_ => _.a, { return: 2 }); + + // act + const result1 = mock.proxy.a; + const err = recordError(() => mock.proxy.a = 3); + const result2 = mock.proxy.a; + + // assert + assert.strictEqual(result1, 2); + assert.strictEqual(result2, 2); + assert.instanceOf(err, Error); + }); + it("set only", () => { + // arrange + const mock = new Mock<{ a: number }>(); + mock.setup(_ => _.a = 2); + + // act + const result = mock.proxy.a; + const err2 = recordError(() => mock.proxy.a = 2); + const err3 = recordError(() => mock.proxy.a = 3); + + // assert + assert.isUndefined(result); + assert.isUndefined(err2); + assert.instanceOf(err3, Error); + }); + it("get and set", () => { + // arrange + const mock = new Mock<{ a: number }>(); + mock.setup(_ => _.a, { return: 2 }); + mock.setup(_ => _.a = Arg.any()); + + // act + const result = mock.proxy.a; + mock.proxy.a = 3; + + // assert + assert.strictEqual(result, 2); + }); + it("method", () => { + // arrange + const mock = new Mock<{ a(x: number): number; }>(); + mock.setup(_ => _.a(1), { return: 2 }); + + // act + const result = mock.proxy.a(1); + + // assert + assert.strictEqual(result, 2); + }); + it("function", () => { + // arrange + const mock = new Mock<(x: number) => number>(x => 0); + mock.setup(_ => _(Arg.number()), { return: 2 }); + + // act + const result = mock.proxy(1); + + // assert + assert.strictEqual(result, 2); + }); + }); }); - it("mock setup setter/getter using callback", () => { - // arrange - const mock = new Mock<{ a: number }>(); - mock.setup(_ => _.a, { returns: 2 }); - mock.setup(_ => _.a = Arg.any()); - // act - const result = mock.value.a; - mock.value.a = 3; + describe("verify", () => { + describe("no setup", () => { + describe("get", () => { + it("not called throws", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target); - // assert - assert.strictEqual(result, 2); - }); - it("mock setup getter only using callback", () => { - // arrange - const mock = new Mock<{ a: number }>(); - mock.setup(_ => _.a, { returns: 2 }); + // act + const e = recordError(() => mock.verify(_ => _.a, Times.once())); - // act - const result = mock.value.a; - const err = recordError(() => mock.value.a = 3); + // assert + assert.instanceOf(e, Error); + }); + it("called passes", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target); + const result = mock.proxy.a; - // assert - assert.strictEqual(result, 2); - assert.instanceOf(err, Error); - }); - it("mock setup setter only using callback", () => { - // arrange - const mock = new Mock<{ a: number }>(); - mock.setup(_ => _.a = 2); + // act + const e = recordError(() => mock.verify(_ => _.a, Times.once())); - // act - const err1 = recordError(() => mock.value.a); - const err2 = recordError(() => mock.value.a = 2); - const err3 = recordError(() => mock.value.a = 3); + // assert + assert.isUndefined(e); + }); + }); + describe("set", () => { + it("not called throws", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target); - // assert - assert.instanceOf(err1, Error); - assert.isUndefined(err2); - assert.instanceOf(err3, Error); - }); - it("mock setup function only using callback", () => { - // arrange - const mock = new Mock<(x: number) => number>(x => 0); - mock.setup(_ => _(Arg.number()), { returns: 2 }); + // act + const e = recordError(() => mock.verify(_ => _.a = 2, Times.once())); - // act - const result = mock.value(1); + // assert + assert.instanceOf(e, Error); + }); + it("called passes", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target); + mock.proxy.a = 2; - // assert - assert.strictEqual(result, 2); + // act + const e = recordError(() => mock.verify(_ => _.a = 2, Times.once())); + + // assert + assert.isUndefined(e); + }); + }); + describe("method", () => { + it("not called throws", () => { + // arrange + const target = { a() { return 1; } }; + const mock = new Mock(target); + + // act + const e = recordError(() => mock.verify(_ => _.a(), Times.once())); + + // assert + assert.instanceOf(e, Error); + }); + it("called passes", () => { + // arrange + const target = { a() { return 1; } }; + const mock = new Mock(target); + const result = mock.proxy.a(); + + // act + const e = recordError(() => mock.verify(_ => _.a(), Times.once())); + + // assert + assert.isUndefined(e); + }); + }); + describe("function", () => { + it("not called throws", () => { + // arrange + const mock = Mock.function(); + + // act + const e = recordError(() => mock.verify(_ => _(), Times.once())); + + // assert + assert.instanceOf(e, Error); + }); + it("called passes", () => { + // arrange + const mock = Mock.function(); + const result = mock.proxy(); + + // act + const e = recordError(() => mock.verify(_ => _(), Times.once())); + + // assert + assert.isUndefined(e); + }); + }); + }); + it("setup get, called passes", () => { + // arrange + const target = { a: 1 }; + const mock = new Mock(target, { get a() { return 2 } }); + const result = mock.proxy.a; + + // act + const e = recordError(() => mock.verify(_ => _.a, Times.once())); + + // assert + assert.isUndefined(e); + }); + it("setup method, called passes", () => { + // arrange + const target = { a(x: number) { return x + 1; } }; + const mock = new Mock(target, { + a(x: number) { + return x + 2; + } + }); + const result = mock.proxy.a(3); + + // act + const e = recordError(() => mock.verify(_ => _.a(Arg.number()), Times.once())); + + // assert + assert.isUndefined(e); + }); }); }); \ No newline at end of file diff --git a/scripts/typemock/src/tests/stubTests.ts b/scripts/typemock/src/tests/stubTests.ts deleted file mode 100644 index 53ea8da20c7..00000000000 --- a/scripts/typemock/src/tests/stubTests.ts +++ /dev/null @@ -1,79 +0,0 @@ -import "./sourceMapSupport"; -import { Mock } from "../mock"; -import { Stub } from "../stub"; -import { Times } from "../times"; -import { assert } from "chai"; - -describe("stub", () => { - it("stub install replaces value", () => { - // arrange - const mock = new Mock({ a: 1 }); - const stub = new Stub(mock.value, "a", 2); - - // act - stub.install(); - - // assert - mock.verify(_ => _.a = 2, Times.once()); - }); - it("stub install is installed", () => { - // arrange - const mock = new Mock({ a: 1 }); - const stub = new Stub(mock.value, "a", 2); - - // act - stub.install(); - - // assert - assert.isTrue(stub.installed); - }); - it("stub install twice only installs once", () => { - // arrange - const mock = new Mock({ a: 1 }); - const stub = new Stub(mock.value, "a", 2); - - // act - stub.install(); - stub.install(); - - // assert - mock.verify(_ => _.a = 2, Times.once()); - }); - it("stub uninstall restores value", () => { - // arrange - const mock = new Mock({ a: 1 }); - const stub = new Stub(mock.value, "a", 2); - stub.install(); - - // act - stub.uninstall(); - - // assert - mock.verify(_ => _.a = 1, Times.once()); - }); - it("stub uninstall is not installed", () => { - // arrange - const mock = new Mock({ a: 1 }); - const stub = new Stub(mock.value, "a", 2); - stub.install(); - - // act - stub.uninstall(); - - // assert - assert.isFalse(stub.installed); - }); - it("stub uninstall twice only uninstalls once", () => { - // arrange - const mock = new Mock({ a: 1 }); - const stub = new Stub(mock.value, "a", 2); - stub.install(); - - // act - stub.uninstall(); - stub.uninstall(); - - // assert - mock.verify(_ => _.a = 1, Times.once()); - }); -}); diff --git a/scripts/typemock/src/tests/timersTests.ts b/scripts/typemock/src/tests/timersTests.ts index 5d3b9d7917a..15c8161facc 100644 --- a/scripts/typemock/src/tests/timersTests.ts +++ b/scripts/typemock/src/tests/timersTests.ts @@ -1,5 +1,5 @@ import "./sourceMapSupport"; -import { Spy } from "../spy"; +import { Mock } from "../mock"; import { Arg } from "../arg"; import { Times } from "../times"; import { Timers } from "../timers"; @@ -10,52 +10,52 @@ describe("timers", () => { it("set adds entry, does not invoke", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - const handle = target.setImmediate(spy.value); + const handle = target.setImmediate(spy.proxy); const pending = target.getPending(); // assert assert.strictEqual(pending.length, 1); assert.strictEqual(pending[0].kind, "immediate"); assert.isDefined(handle); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("set/clear", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - const handle = target.setImmediate(spy.value); + const handle = target.setImmediate(spy.proxy); target.clearImmedate(handle); const pending = target.getPending(); // assert assert.strictEqual(pending.length, 0); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("set one and execute", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.setImmediate(spy.value); + target.setImmediate(spy.proxy); const count = target.executeImmediates(); // assert assert.strictEqual(count, 1); - spy.called(Times.once()); + spy.verify(_ => _(), Times.once()); }); it("set one with arg and execute", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.setImmediate(spy.value, "a"); + target.setImmediate(spy.proxy, "a"); const count = target.executeImmediates(); // assert @@ -65,108 +65,108 @@ describe("timers", () => { it("nested with maxDepth = 0", () => { // arrange const target = new Timers(); - const spy = new Spy(() => { target.setImmediate(spy.value); }); + const spy = Mock.spy(() => { target.setImmediate(spy.proxy); }); // act - target.setImmediate(spy.value); + target.setImmediate(spy.proxy); const count = target.executeImmediates(/*maxDepth*/ 0); // assert assert.strictEqual(count, 1); - spy.called(Times.once()); + spy.verify(_ => _(), Times.once()); }); it("nested with maxDepth = 1", () => { // arrange const target = new Timers(); - const spy = new Spy(() => { target.setImmediate(spy.value); }); + const spy = Mock.spy(() => { target.setImmediate(spy.proxy); }); // act - target.setImmediate(spy.value); + target.setImmediate(spy.proxy); const count = target.executeImmediates(/*maxDepth*/ 1); // assert assert.strictEqual(count, 2); - spy.called(Times.exactly(2)); + spy.verify(_ => _(), Times.exactly(2)); }); }); describe("timeout", () => { it("set adds entry, does not invoke", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - const handle = target.setTimeout(spy.value, 0); + const handle = target.setTimeout(spy.proxy, 0); const pending = target.getPending(); // assert assert.strictEqual(pending.length, 1); assert.strictEqual(pending[0].kind, "timeout"); assert.isDefined(handle); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("set/clear", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - const handle = target.setTimeout(spy.value, 0); + const handle = target.setTimeout(spy.proxy, 0); target.clearTimeout(handle); const pending = target.getPending(); // assert assert.strictEqual(pending.length, 0); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("set adds future entry, advance prior to due does not invoke", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.setTimeout(spy.value, 10); + target.setTimeout(spy.proxy, 10); const count = target.advance(9); // assert assert.strictEqual(count, 0); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("set adds future entry, advance to due invokes", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.setTimeout(spy.value, 10); + target.setTimeout(spy.proxy, 10); const count = target.advance(10); // assert assert.strictEqual(count, 1); - spy.called(Times.once()); + spy.verify(_ => _(), Times.once()); }); it("5 nested sets throttle", () => { // arrange const target = new Timers(); - const spy = new Spy(() => { target.setTimeout(spy.value, 0); }); + const spy = new Mock(() => { target.setTimeout(spy.proxy, 0); }); // act - target.setTimeout(spy.value, 0); + target.setTimeout(spy.proxy, 0); const count = target.advance(1); // assert assert.strictEqual(count, 5); - spy.called(Times.exactly(5)); + spy.verify(_ => _(), Times.exactly(5)); }); }); describe("interval", () => { it("set adds entry, does not invoke", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - const handle = target.setInterval(spy.value, 0); + const handle = target.setInterval(spy.proxy, 0); const pending = target.getPending({ kind: "interval", ms: 10 }); // assert @@ -174,132 +174,132 @@ describe("timers", () => { assert.strictEqual(pending[0].kind, "interval"); assert.strictEqual(pending[0].interval, 10); assert.isDefined(handle); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("set/clear", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - const handle = target.setInterval(spy.value, 0); + const handle = target.setInterval(spy.proxy, 0); target.clearInterval(handle); const pending = target.getPending({ kind: "interval", ms: 10 }); // assert assert.strictEqual(pending.length, 0); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("set adds future entry, advance prior to due does not invoke", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.setInterval(spy.value, 10); + target.setInterval(spy.proxy, 10); const count = target.advance(9); // assert assert.strictEqual(count, 0); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("set adds future entry, advance to due invokes", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.setInterval(spy.value, 10); + target.setInterval(spy.proxy, 10); const count = target.advance(10); // assert assert.strictEqual(count, 1); - spy.called(Times.once()); + spy.verify(_ => _(), Times.once()); }); it("set adds future entry, advance to due twice invokes twice", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.setInterval(spy.value, 10); + target.setInterval(spy.proxy, 10); const count = target.advance(20); // assert assert.strictEqual(count, 2); - spy.called(Times.exactly(2)); + spy.verify(_ => _(), Times.exactly(2)); }); it("set adds future entry, remove before second due time", () => { // arrange const target = new Timers(); - const spy = new Spy(() => { target.clearInterval(handle); }); + const spy = new Mock(() => { target.clearInterval(handle); }); // act - const handle = target.setInterval(spy.value, 10); + const handle = target.setInterval(spy.proxy, 10); const count = target.advance(20); // assert assert.strictEqual(count, 1); - spy.called(Times.exactly(1)); + spy.verify(_ => _(), Times.exactly(1)); }); }); describe("frame", () => { it("request adds entry, does not invoke", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - const handle = target.requestAnimationFrame(spy.value); + const handle = target.requestAnimationFrame(spy.proxy); const pending = target.getPending({ ms: 16 }); // assert assert.strictEqual(pending.length, 1); assert.strictEqual(pending[0].kind, "frame"); assert.isDefined(handle); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("request/cancel", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - const handle = target.requestAnimationFrame(spy.value); + const handle = target.requestAnimationFrame(spy.proxy); target.cancelAnimationFrame(handle); const pending = target.getPending(); // assert assert.strictEqual(pending.length, 0); - spy.called(Times.none()); + spy.verify(_ => _(), Times.none()); }); it("request and advance past one frame", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.requestAnimationFrame(spy.value); + target.requestAnimationFrame(spy.proxy); const count = target.advance(16); // assert assert.strictEqual(count, 1); - spy.called(Times.once()); + spy.verify(_ => _(Arg.number()), Times.once()); }); it("requests clamped to 16ms", () => { // arrange const target = new Timers(); - const spy = new Spy(); + const spy = Mock.function(); // act - target.requestAnimationFrame(spy.value); + target.requestAnimationFrame(spy.proxy); target.advance(10); - target.requestAnimationFrame(spy.value); + target.requestAnimationFrame(spy.proxy); const count = target.advance(16); // assert assert.strictEqual(count, 2); - spy.called(Times.exactly(2)); + spy.verify(_ => _(Arg.number()), Times.exactly(2)); }); }); }); \ No newline at end of file diff --git a/src/harness/mocks.ts b/src/harness/fakes.ts similarity index 90% rename from src/harness/mocks.ts rename to src/harness/fakes.ts index 97ea4b1e0ee..6fae3658104 100644 --- a/src/harness/mocks.ts +++ b/src/harness/fakes.ts @@ -3,12 +3,12 @@ /// /// -// NOTE: The contents of this file are all exported from the namespace 'mocks'. This is to +// NOTE: The contents of this file are all exported from the namespace 'fakes'. This is to // support the eventual conversion of harness into a modular system. -// harness mocks -namespace mocks { - export interface MockServerHostOptions { +// harness fakes +namespace fakes { + export interface FakeServerHostOptions { /** * The `VirtualFleSystem` to use. If not specified, a new case-sensitive `VirtualFileSystem` * is created. @@ -32,7 +32,7 @@ namespace mocks { lib?: boolean; } - export class MockServerHost implements ts.server.ServerHost, ts.FormatDiagnosticsHost { + export class FakeServerHost implements ts.server.ServerHost, ts.FormatDiagnosticsHost { public static readonly defaultExecutingFilePath = "/.ts/tsc.js"; public static readonly defaultCurrentDirectory = "/"; public static readonly safeListPath = "/safelist.json"; @@ -67,16 +67,16 @@ namespace mocks { private readonly _executingFilePath: string; private readonly _getCanonicalFileName: (file: string) => string; - constructor(options: MockServerHostOptions = {}) { + constructor(options: FakeServerHostOptions = {}) { const { vfs: _vfs = {}, - executingFilePath = MockServerHost.defaultExecutingFilePath, + executingFilePath = FakeServerHost.defaultExecutingFilePath, newLine = "\n", safeList = false, lib = false } = options; - const { currentDirectory = MockServerHost.defaultCurrentDirectory, useCaseSensitiveFileNames = false } = _vfs; + const { currentDirectory = FakeServerHost.defaultCurrentDirectory, useCaseSensitiveFileNames = false } = _vfs; this.vfs = _vfs instanceof vfs.VirtualFileSystem ? _vfs : new vfs.VirtualFileSystem(currentDirectory, useCaseSensitiveFileNames); @@ -87,11 +87,11 @@ namespace mocks { this._getCanonicalFileName = ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames); if (safeList) { - this.vfs.addFile(MockServerHost.safeListPath, MockServerHost.safeListContent); + this.vfs.addFile(FakeServerHost.safeListPath, FakeServerHost.safeListContent); } if (lib) { - this.vfs.addFile(MockServerHost.libPath, MockServerHost.libContent); + this.vfs.addFile(FakeServerHost.libPath, FakeServerHost.libContent); } } @@ -139,7 +139,7 @@ namespace mocks { public exit(exitCode?: number) { this.exitCode = exitCode; - throw MockServerHost.processExitSentinel; + throw FakeServerHost.processExitSentinel; } // #endregion DirectoryStructureHost members @@ -241,7 +241,7 @@ namespace mocks { this.timers.advanceToEnd(); } catch (e) { - if (e !== MockServerHost.processExitSentinel) { + if (e !== FakeServerHost.processExitSentinel) { throw e; } } diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 06bc299d598..cf533cd03dc 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -73,7 +73,7 @@ "../services/codefixes/disableJsDiagnostics.ts", "harness.ts", - + "core.ts", "utils.ts", "typemock.ts", @@ -82,7 +82,7 @@ "vpath.ts", "vfs.ts", "compiler.ts", - "mocks.ts", + "fakes.ts", "virtualFileSystemWithWatch.ts", "sourceMapRecorder.ts", diff --git a/src/harness/typemock.ts b/src/harness/typemock.ts index 32950dfac74..f64af2bffb3 100644 --- a/src/harness/typemock.ts +++ b/src/harness/typemock.ts @@ -7,92 +7,40 @@ // typemock library namespace typemock { - type Imported = T["prototype"]; + const module = require("typemock"); + typemock.Arg = module.Arg; + typemock.Times = module.Times; + typemock.Mock = module.Mock; + typemock.Spy = module.Spy; + typemock.Inject = module.Inject; + typemock.Timers = module.Timers; + typemock.spy = module.spy; +} - function unwrap(module: any, _: () => PromiseLike): T { return module; } - - const module = unwrap(require("../../scripts/typemock"), () => import("../../scripts/typemock")); - - export const Arg = module.Arg; - - export interface Arg extends Imported { - } - - export interface Returns { - returns: U; - } - - export interface Throws { - throws: any; - } - - export const Mock = module.Mock; - - export interface Mock extends Imported { - readonly value: T; - setup(callback: (value: T) => U, result?: Returns | Throws): Mock; - setup(setups: Partial): Mock; - verify(callback: (value: T) => any, times: Times): Mock; - } - - export type Callable = ((...args: any[]) => any); - - export type Constructable = (new (...args: any[]) => any); - - export const Spy = module.Spy; - - export interface Spy extends Imported { - readonly value: T; - verify(callback: (value: T) => any, times: Times): this; - } - - export const Times = module.Times; - - export interface Times extends Imported { - } - - 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 declare type Timer = Immediate | Timeout | Interval | AnimationFrame; - - export const Timers = module.Timers; - - export interface Timers extends Imported { - } - - export const Stub = module.Stub; - - export interface Stub extends Imported { - readonly target: T; - readonly key: K; - stubValue: T[K]; - readonly originalValue: T[K]; - readonly currentValue: T[K]; +declare module "typemock_" { + import * as typemock_ from "typemock"; + global { + namespace typemock { + export import Arg = typemock_.Arg; + export import Times = typemock_.Times; + export import Mock = typemock_.Mock; + export import Spy = typemock_.Spy; + export import Returns = typemock_.Returns; + export import Throws = typemock_.Throws; + export import ThisArg = typemock_.ThisArg; + export import Callback = typemock_.Callback; + export import Fallback = typemock_.Fallback; + export import Setup = typemock_.Setup; + export import Callable = typemock_.Callable; + export import Constructable = typemock_.Constructable; + export import Inject = typemock_.Inject; + export import Timers = typemock_.Timers; + export import Timer = typemock_.Timer; + export import Timeout = typemock_.Timeout; + export import Interval = typemock_.Interval; + export import Immediate = typemock_.Immediate; + export import AnimationFrame = typemock_.AnimationFrame; + export import spy = typemock_.spy; + } } } \ No newline at end of file diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 12fc8127732..d462d55b4be 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -2,7 +2,7 @@ /// /// /// -/// +/// namespace ts.projectSystem { const nullCancellationToken = server.nullCancellationToken; @@ -62,7 +62,7 @@ namespace ts.projectSystem { describe("for configured projects", () => { it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -109,7 +109,7 @@ namespace ts.projectSystem { }); it("should be up-to-date with the reference map changes", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -176,7 +176,7 @@ namespace ts.projectSystem { }); it("should be up-to-date with changes made in non-open files", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -211,7 +211,7 @@ namespace ts.projectSystem { }); it("should be up-to-date with deleted files", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -245,7 +245,7 @@ namespace ts.projectSystem { }); it("should be up-to-date with newly created files", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -279,7 +279,7 @@ namespace ts.projectSystem { }); it("should detect changes in non-root files", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ "compileOnSave": true, "files": ["${file1Consumer1.path}"] }`); @@ -323,7 +323,7 @@ namespace ts.projectSystem { }); it("should return all files if a global file changed shape", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -352,7 +352,7 @@ namespace ts.projectSystem { }); it("should return empty array if CompileOnSave is not enabled", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{}`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -371,7 +371,7 @@ namespace ts.projectSystem { }); it("should save when compileOnSave is enabled in base tsconfig.json", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ "extends": "/a/tsconfig.json" }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -391,7 +391,7 @@ namespace ts.projectSystem { }); it("should always return the file itself if '--isolatedModules' is specified", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ "compileOnSave": true, "compilerOptions": { "isolatedModules": true } }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -416,7 +416,7 @@ namespace ts.projectSystem { }); it("should always return the file itself if '--out' or '--outFile' is specified", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ "compileOnSave": true, "compilerOptions": { "module": "system", "outFile": "/a/b/out.js" } }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -441,7 +441,7 @@ namespace ts.projectSystem { }); it("should return cascaded affected file list", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const file1Consumer1 = host.vfs.addFile("/a/b/file1Consumer1.ts", `import {Foo} from "./moduleFile1"; export var y = 10;`); @@ -481,7 +481,7 @@ namespace ts.projectSystem { }); it("should work fine for files with circular references", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const file1 = host.vfs.addFile("/a/b/file1.ts", `/// \nexport var t1 = 10;`); const file2 = host.vfs.addFile("/a/b/file2.ts", `/// \nexport var t2 = 10;`); @@ -496,7 +496,7 @@ namespace ts.projectSystem { }); it("should return results for all projects if not specifying projectFileName", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const file1 = host.vfs.addFile("/a/b/file1.ts", `export var t = 10;`); const file2 = host.vfs.addFile("/a/b/file2.ts", `import {t} from "./file1"; var t2 = 11;`); const file3 = host.vfs.addFile("/a/c/file2.ts", `import {t} from "../b/file1"; var t3 = 11;`); @@ -516,7 +516,7 @@ namespace ts.projectSystem { }); it("should detect removed code file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const moduleFile1 = host.vfs.addFile("/a/b/moduleFile1.ts", `export function Foo() { };`); const referenceFile1 = host.vfs.addFile("/a/b/referenceFile1.ts", `/// \nexport var x = Foo();`); @@ -537,7 +537,7 @@ namespace ts.projectSystem { }); it("should detect non-existing code file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{ compileOnSave": true }`); const referenceFile1 = host.vfs.addFile("/a/b/referenceFile1.ts", `/// \nexport var x = Foo();`); @@ -558,7 +558,7 @@ namespace ts.projectSystem { test("\r\n"); function test(newLine: "\r\n" | "\n") { - const host = new mocks.MockServerHost({ newLine }); + const host = new fakes.FakeServerHost({ newLine }); const f = host.vfs.addFile("/a/app.ts", `var x = 1;${newLine}var y = 2;`); const session = createSession(host); @@ -572,7 +572,7 @@ namespace ts.projectSystem { }); it("should emit specified file", () => { - const host = new mocks.MockServerHost({ newLine: "\r\n" }); + const host = new fakes.FakeServerHost({ newLine: "\r\n" }); const file1 = host.vfs.addFile("/a/b/f1.ts", `export function Foo() { return 10; }`); const file2 = host.vfs.addFile("/a/b/f2.ts", `import {Foo} from "./f1"; let y = Foo();`); const configFile = host.vfs.addFile("/a/b/tsconfig.json", `{}`); @@ -593,7 +593,7 @@ namespace ts.projectSystem { it("shoud not emit js files in external projects", () => { const externalProjectName = "/a/b/externalproject"; - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const file1 = host.vfs.addFile("/a/b/file1.ts", `consonle.log('file1');`); // file2 has errors. The emit should not be blocked. const file2 = host.vfs.addFile("/a/b/file2.js", `console.log'file2');`); @@ -626,7 +626,7 @@ namespace ts.projectSystem { it("should use project root as current directory so that compile on save results in correct file mapping", () => { const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const file1 = host.vfs.addFile("/root/TypeScriptProject3/TypeScriptProject3/Foo.ts", `consonle.log('file1');`); host.vfs.addFile(libFile.path, libFile.content); diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 7ea2af973d0..92cb5e1c9d9 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -1,7 +1,7 @@ /// /// /// -/// +/// namespace ts.projectSystem { describe("Project errors", () => { @@ -31,7 +31,7 @@ namespace ts.projectSystem { } it("external project - diagnostics for missing files", () => { - const host = new mocks.MockServerHost({ safeList: true, lib: true }); + const host = new fakes.FakeServerHost({ safeList: true, lib: true }); host.vfs.addFile("/a/b/app.ts", ``); const projectFileName = "/a/b/test.csproj"; @@ -66,7 +66,7 @@ namespace ts.projectSystem { }); it("configured projects - diagnostics for missing files", () => { - const host = new mocks.MockServerHost({ safeList: true, lib: true }); + const host = new fakes.FakeServerHost({ safeList: true, lib: true }); host.vfs.addFile("/a/b/app.ts", ``); host.vfs.addFile("/a/b/tsconfig.json", `{ "files": ["app.ts", "applib.ts"] }`); @@ -88,7 +88,7 @@ namespace ts.projectSystem { }); it("configured projects - diagnostics for corrupted config 1", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); host.vfs.addFile("/a/b/app.ts", ``); host.vfs.addFile("/a/b/lib.ts", ``); host.vfs.addFile("/a/b/tsconfig.json", ` "files": ["app.ts", "lib.ts"] }`); @@ -122,7 +122,7 @@ namespace ts.projectSystem { }); it("configured projects - diagnostics for corrupted config 2", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); host.vfs.addFile("/a/b/app.ts", ``); host.vfs.addFile("/a/b/lib.ts", ``); host.vfs.addFile("/a/b/tsconfig.json", `{ "files": ["app.ts", "lib.ts"] }`); diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 83bf2217db5..7219c58937b 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -1,5 +1,6 @@ /// /// +/// namespace ts { @@ -915,7 +916,7 @@ namespace ts { } function verifyProgram(vfs: vfs.VirtualFileSystem, rootFiles: string[], options: CompilerOptions, configFile: string) { - const watchingSystemHost = createWatchingSystemHost(new mocks.MockServerHost({ vfs })); + const watchingSystemHost = createWatchingSystemHost(new fakes.FakeServerHost({ vfs })); verifyProgramWithoutConfigFile(watchingSystemHost, rootFiles, options); verifyProgramWithConfigFile(watchingSystemHost, configFile); } @@ -997,7 +998,7 @@ namespace ts { `export default classD;`); const configFile = fs.addFile("/src/tsconfig.json", JSON.stringify({ compilerOptions, include: ["packages/**/ *.ts"] })); - const watchingSystemHost = createWatchingSystemHost(new mocks.MockServerHost({ vfs: fs })); + const watchingSystemHost = createWatchingSystemHost(new fakes.FakeServerHost({ vfs: fs })); verifyProgramWithConfigFile(watchingSystemHost, configFile.path); }); }); diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 22ec81d583e..7719b2c9937 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1,12 +1,12 @@ /// /// /// -/// +/// /// namespace ts.tscWatch { import theory = utils.theory; - import Spy = typemock.Spy; + import spy = typemock.spy; import Arg = typemock.Arg; import Times = typemock.Times; @@ -63,7 +63,7 @@ namespace ts.tscWatch { return result; } - function checkOutputErrors(host: mocks.MockServerHost, errors: ReadonlyArray, isInitial?: true, skipWaiting?: true) { + function checkOutputErrors(host: fakes.FakeServerHost, errors: ReadonlyArray, isInitial?: true, skipWaiting?: true) { const outputs = host.getOutput(); const expectedOutputCount = (isInitial ? 0 : 1) + errors.length + (skipWaiting ? 0 : 1); assert.equal(outputs.length, expectedOutputCount, "Outputs = " + outputs.toString()); @@ -82,17 +82,17 @@ namespace ts.tscWatch { host.clearOutput(); } - function assertDiagnosticAt(host: mocks.MockServerHost, outputAt: number, diagnostic: Diagnostic) { + function assertDiagnosticAt(host: fakes.FakeServerHost, outputAt: number, diagnostic: Diagnostic) { const output = host.getOutput()[outputAt]; assert.equal(output, formatDiagnostic(diagnostic, host), "outputs[" + outputAt + "] is " + output); } - function assertWatchDiagnosticAt(host: mocks.MockServerHost, outputAt: number, diagnosticMessage: DiagnosticMessage) { + function assertWatchDiagnosticAt(host: fakes.FakeServerHost, outputAt: number, diagnosticMessage: DiagnosticMessage) { const output = host.getOutput()[outputAt]; assert.isTrue(endsWith(output, getWatchDiagnosticWithoutDate(host, diagnosticMessage)), "outputs[" + outputAt + "] is " + output); } - function getWatchDiagnosticWithoutDate(host: mocks.MockServerHost, diagnosticMessage: DiagnosticMessage) { + function getWatchDiagnosticWithoutDate(host: fakes.FakeServerHost, diagnosticMessage: DiagnosticMessage) { return ` - ${flattenDiagnosticMessageText(getLocaleSpecificMessage(diagnosticMessage), host.newLine)}${host.newLine + host.newLine + host.newLine}`; } @@ -159,12 +159,12 @@ namespace ts.tscWatch { describe("program updates", () => { it("create watch without config file", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/c/app.ts", `import {f} from "./module"\nconsole.log(f)`); host.vfs.addFile("/a/b/c/module.d.ts", `export let x: number`); const watch = createWatchModeWithoutConfigFile(["/a/b/c/app.ts"], host); - checkProgramActualFiles(watch(), ["/a/b/c/app.ts", mocks.MockServerHost.libPath, "/a/b/c/module.d.ts"]); + checkProgramActualFiles(watch(), ["/a/b/c/app.ts", fakes.FakeServerHost.libPath, "/a/b/c/module.d.ts"]); // TODO: Should we watch creation of config files in the root file's file hierarchy? @@ -174,7 +174,7 @@ namespace ts.tscWatch { }); it("can handle tsconfig file name with difference casing", () => { - const host = new mocks.MockServerHost({ vfs: { useCaseSensitiveFileNames: false } }); + const host = new fakes.FakeServerHost({ vfs: { useCaseSensitiveFileNames: false } }); host.vfs.addFile("/a/b/app.ts", `let x = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{ "include": ["app.ts"] }`); @@ -183,7 +183,7 @@ namespace ts.tscWatch { }); it("create configured project without file list", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {}, "exclude": ["e"] }`); host.vfs.addFile("/a/b/c/f1.ts", `let x = 1`); host.vfs.addFile("/a/b/d/f2.ts", `let y = 1`); @@ -195,9 +195,9 @@ namespace ts.tscWatch { const watch = ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost); - checkProgramActualFiles(watch(), ["/a/b/c/f1.ts", mocks.MockServerHost.libPath, "/a/b/d/f2.ts"]); + checkProgramActualFiles(watch(), ["/a/b/c/f1.ts", fakes.FakeServerHost.libPath, "/a/b/d/f2.ts"]); checkProgramRootFiles(watch(), ["/a/b/c/f1.ts", "/a/b/d/f2.ts"]); - checkWatchedFiles(host, ["/a/b/tsconfig.json", "/a/b/c/f1.ts", "/a/b/d/f2.ts", mocks.MockServerHost.libPath]); + checkWatchedFiles(host, ["/a/b/tsconfig.json", "/a/b/c/f1.ts", "/a/b/d/f2.ts", fakes.FakeServerHost.libPath]); checkWatchedDirectories(host, ["/a/b", "/a/b/node_modules/@types"], /*recursive*/ true); }); @@ -206,7 +206,7 @@ namespace ts.tscWatch { // }); it("add new files to a configured program without file list", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/commonFile1.ts", `let x = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{}`); @@ -222,7 +222,7 @@ namespace ts.tscWatch { }); it("should ignore non-existing files specified in the config file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/commonFile1.ts", `let x = 1`); host.vfs.addFile("/a/b/commonFile2.ts", `let y = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {}, "files": ["commonFile1.ts", "commonFile3.ts"] }`); @@ -233,7 +233,7 @@ namespace ts.tscWatch { }); it("handle recreated files correctly", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/commonFile1.ts", `let x = 1`); host.vfs.addFile("/a/b/commonFile2.ts", `let y = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{}`); @@ -255,13 +255,13 @@ namespace ts.tscWatch { it("handles the missing files - that were added to program because they were added with /// { const file1Content = `/// \nlet x = y`; - const host = new mocks.MockServerHost({ lib: true, vfs: { useCaseSensitiveFileNames: false } }); + const host = new fakes.FakeServerHost({ lib: true, vfs: { useCaseSensitiveFileNames: false } }); host.vfs.addFile("/a/b/commonFile1.ts", file1Content); const watch = createWatchModeWithoutConfigFile(["/a/b/commonFile1.ts"], host); checkProgramRootFiles(watch(), ["/a/b/commonFile1.ts"]); - checkProgramActualFiles(watch(), ["/a/b/commonFile1.ts", mocks.MockServerHost.libPath]); + checkProgramActualFiles(watch(), ["/a/b/commonFile1.ts", fakes.FakeServerHost.libPath]); checkOutputErrors(host, [ createFileNotFoundDiagnostic(watch(), "/a/b/commonFile1.ts", file1Content, "commonFile2.ts", "/a/b/commonFile2.ts"), createCannotFindNameDiagnostic(watch(), "/a/b/commonFile1.ts", file1Content, "y"), @@ -270,12 +270,12 @@ namespace ts.tscWatch { host.vfs.addFile("/a/b/commonFile2.ts", `let y = 1`); host.checkTimeoutQueueLengthAndRun(1); checkProgramRootFiles(watch(), ["/a/b/commonFile1.ts"]); - checkProgramActualFiles(watch(), ["/a/b/commonFile1.ts", mocks.MockServerHost.libPath, "/a/b/commonFile2.ts"]); + checkProgramActualFiles(watch(), ["/a/b/commonFile1.ts", fakes.FakeServerHost.libPath, "/a/b/commonFile2.ts"]); checkOutputErrors(host, emptyArray); }); it("should reflect change in config file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/commonFile1.ts", `let x = 1`); host.vfs.addFile("/a/b/commonFile2.ts", `let y = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {}, "files": ["/a/b/commonFile1.ts", "/a/b/commonFile2.ts"] }`); @@ -289,7 +289,7 @@ namespace ts.tscWatch { }); it("files explicitly excluded in config file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/commonFile1.ts", `let x = 1`); host.vfs.addFile("/a/b/commonFile2.ts", `let y = 1`); host.vfs.addFile("/a/c/excludedFile1.ts", `let t = 1;`); @@ -300,7 +300,7 @@ namespace ts.tscWatch { }); it("should properly handle module resolution changes in config file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/file1.ts", `import { T } from "module1";`); host.vfs.addFile("/a/b/node_modules/module1.ts", `export interface T {}`); host.vfs.addFile("/a/module1.ts", `export interface T {}`); @@ -317,7 +317,7 @@ namespace ts.tscWatch { }); it("should tolerate config file errors and still try to build a project", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/commonFile1.ts", `let x = 1`); host.vfs.addFile("/a/b/commonFile2.ts", `let y = 1`); host.vfs.addFile("/a/b/tsconfig.json", @@ -334,7 +334,7 @@ namespace ts.tscWatch { }); it("changes in files are reflected in project structure", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/f1.ts", `export * from "./f2"`); host.vfs.addFile("/a/b/f2.ts", `export let x = 1`); host.vfs.addFile("/a/c/f3.ts", `export let y = 1;`); @@ -350,7 +350,7 @@ namespace ts.tscWatch { }); it("deleted files affect project structure", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/f1.ts", `export * from "./f2"`); host.vfs.addFile("/a/b/f2.ts", `export * from "../c/f3"`); host.vfs.addFile("/a/c/f3.ts", `export let y = 1;`); @@ -364,7 +364,7 @@ namespace ts.tscWatch { }); it("deleted files affect project structure - 2", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/f1.ts", `export * from "./f2"`); host.vfs.addFile("/a/b/f2.ts", `export * from "../c/f3"`); host.vfs.addFile("/a/c/f3.ts", `export let y = 1;`); @@ -378,7 +378,7 @@ namespace ts.tscWatch { }); it("config file includes the file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/f1.ts", `export let x = 5`); host.vfs.addFile("/a/c/f2.ts", `import {x} from "../b/f1"`); host.vfs.addFile("/a/c/f3.ts", `export let y = 1`); @@ -390,7 +390,7 @@ namespace ts.tscWatch { }); it("correctly migrate files between projects", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/f1.ts", `export * from "../c/f2";\n` + `export * from "../d/f3";`); @@ -409,7 +409,7 @@ namespace ts.tscWatch { }); it("can correctly update configured project when set of root files has changed (new file on disk)", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/f1.ts", `let x = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {} }`); @@ -423,7 +423,7 @@ namespace ts.tscWatch { }); it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/f1.ts", `let x = 1`); host.vfs.addFile("/a/b/f2.ts", `let y = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {}, "files": ["f1.ts"] }`); @@ -438,7 +438,7 @@ namespace ts.tscWatch { }); it("can update configured project when set of root files was not changed", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/f1.ts", `let x = 1`); host.vfs.addFile("/a/b/f2.ts", `let y = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {}, "files": ["f1.ts", "f2.ts"] }`); @@ -453,14 +453,14 @@ namespace ts.tscWatch { }); it("config file is deleted", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/f1.ts", `let x = 1`); host.vfs.addFile("/a/b/f2.ts", `let y = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {} }`); const watch = createWatchModeWithConfigFile("/a/b/tsconfig.json", host); - checkProgramActualFiles(watch(), ["/a/b/f1.ts", "/a/b/f2.ts", mocks.MockServerHost.libPath]); + checkProgramActualFiles(watch(), ["/a/b/f1.ts", "/a/b/f2.ts", fakes.FakeServerHost.libPath]); checkOutputErrors(host, emptyArray, /*isInitial*/ true); host.vfs.removeFile("/a/b/tsconfig.json"); @@ -473,7 +473,7 @@ namespace ts.tscWatch { }); it("Proper errors: document is not contained in project", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/app.ts", ``); host.vfs.addFile("/a/b/tsconfig.json", `{`); @@ -482,7 +482,7 @@ namespace ts.tscWatch { }); it("correctly handles changes in lib section of config file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/.ts/lib.es5.d.ts", `declare const eval: any`); host.vfs.addFile("/.ts/lib.es2015.promise.d.ts", `declare class Promise {}`); host.vfs.addFile("/src/app.ts", `var x: Promise;`); @@ -497,7 +497,7 @@ namespace ts.tscWatch { }); it("should handle non-existing directories in config file", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/src/app.ts", `let x = 1;`); host.vfs.addFile("/a/tsconfig.json", `{ "compilerOptions": {}, "include": ["src/**/*", "notexistingfolder/*"] }`); @@ -507,7 +507,7 @@ namespace ts.tscWatch { it("rename a module file and rename back should restore the states for inferred projects", () => { const file1Content = `import * as T from "./moduleFile"; T.bar();`; - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/file1.ts", file1Content); host.vfs.addFile("/a/b/moduleFile.ts", `export function bar() { };`); @@ -531,7 +531,7 @@ namespace ts.tscWatch { it("rename a module file and rename back should restore the states for configured projects", () => { const file1Content = `import * as T from "./moduleFile"; T.bar();`; - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/file1.ts", file1Content); host.vfs.addFile("/a/b/moduleFile.ts", `export function bar() { };`); host.vfs.addFile("/a/b/tsconfig.json", `{}`); @@ -555,7 +555,7 @@ namespace ts.tscWatch { }); it("types should load from config file path if config exists", () => { - const host = new mocks.MockServerHost({ vfs: { currentDirectory: "/a/c" }}); + const host = new fakes.FakeServerHost({ vfs: { currentDirectory: "/a/c" } }); host.vfs.addDirectory("/a/c"); host.vfs.addFile("/a/b/app.ts", `let x = 1`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": { "types": ["node"], "typeRoots": [] } }`); @@ -567,7 +567,7 @@ namespace ts.tscWatch { it("add the missing module file for inferred project: should remove the `module not found` error", () => { const file1Content = `import * as T from "./moduleFile"; T.bar();`; - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/file1.ts", file1Content); const watch = createWatchModeWithoutConfigFile(["/a/b/file1.ts"], host); @@ -584,7 +584,7 @@ namespace ts.tscWatch { it("Configure file diagnostics events are generated when the config file has errors", () => { const configFileContent = `{ "compilerOptions": { "foo": "bar", "allowJS": true } }`; - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/app.ts", `let x = 10`); host.vfs.addFile("/a/b/tsconfig.json", configFileContent); @@ -596,7 +596,7 @@ namespace ts.tscWatch { }); it("If config file doesnt have errors, they are not reported", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/app.ts", `let x = 10`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {} }`); @@ -605,7 +605,7 @@ namespace ts.tscWatch { }); it("Reports errors when the config file changes", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/app.ts", `let x = 10`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {} }`); @@ -627,16 +627,16 @@ namespace ts.tscWatch { }); it("non-existing directories listed in config file input array should be tolerated without crashing the server", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/file1.ts", `let t = 10;`); host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": {}, "include": ["app/*", "test/**/*", "something"] }`); const watch = createWatchModeWithConfigFile("/a/b/tsconfig.json", host); - checkProgramActualFiles(watch(), [mocks.MockServerHost.libPath]); + checkProgramActualFiles(watch(), [fakes.FakeServerHost.libPath]); }); it("non-existing directories listed in config file input array should be able to handle @types if input file list is empty", () => { - const host = new mocks.MockServerHost({ vfs: { currentDirectory: "/a/" } }); + const host = new fakes.FakeServerHost({ vfs: { currentDirectory: "/a/" } }); host.vfs.addFile("/a/app.ts", `let x = 1`); host.vfs.addFile("/a/tsconfig.json", `{ "compilerOptions": {}, "files": [] }`); host.vfs.addFile("/a/node_modules/@types/typings/index.d.ts", `export * from "./lib"`); @@ -648,11 +648,11 @@ namespace ts.tscWatch { }); it("should support files without extensions", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/compile", `let x = 1`); const watch = createWatchModeWithoutConfigFile(["/a/compile"], host, { allowNonTsExtensions: true }); - checkProgramActualFiles(watch(), ["/a/compile", mocks.MockServerHost.libPath]); + checkProgramActualFiles(watch(), ["/a/compile", fakes.FakeServerHost.libPath]); }); it("Options Diagnostic locations reported correctly with changes in configFile contents when options change", () => { @@ -675,7 +675,7 @@ namespace ts.tscWatch { ` }\n` + `}`; - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/b/app.ts", `let x = 10`); host.vfs.addFile("/a/b/tsconfig.json", configFileContentWithComment); @@ -713,13 +713,7 @@ namespace ts.tscWatch { ] }); - const filesWritten = new Map(); - const host = new class extends mocks.MockServerHost { - writeFile(path: string, data: string) { - filesWritten.set(path, (filesWritten.get(path) || 0) + 1); - super.writeFile(path, data); - } - }; + const host = new fakes.FakeServerHost(); host.vfs.addFile("/a/b/output/AnotherDependency/file1.d.ts", `declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }`); host.vfs.addFile("/a/b/dependencies/file2.d.ts", `declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }`); @@ -727,20 +721,23 @@ namespace ts.tscWatch { host.vfs.addFile("/a/b/project/src/main2.ts", `namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }`); host.vfs.addFile("/a/b/project/tsconfig.json", configContent); + const writeFileSpy = spy(host, "writeFile"); + createWatchModeWithConfigFile("/a/b/project/tsconfig.json", host); if (useOutFile) { - // Only out file - assert.equal(filesWritten.size, 1); + writeFileSpy + .verify(_ => _("/a/b/output/common.js", Arg.string(), Arg.any()), Times.once()) + .verify(_ => _(Arg.string(), Arg.string(), Arg.any()), Times.once()) + .revoke(); } else { - // main.js and main2.js - assert.equal(filesWritten.size, 2); + writeFileSpy + .verify(_ => _("/a/b/output/main.js", Arg.string(), Arg.any()), Times.once()) + .verify(_ => _("/a/b/output/main2.js", Arg.string(), Arg.any()), Times.once()) + .verify(_ => _(Arg.string(), Arg.string(), Arg.any()), Times.exactly(2)) + .revoke(); } - - filesWritten.forEach((value, key) => { - assert.equal(value, 1, "Key: " + key); - }); } it("with --outFile and multiple declaration files in the program", () => { @@ -753,22 +750,22 @@ namespace ts.tscWatch { }); describe("emit", () => { - function writeFile(host: mocks.MockServerHost, path: string, content: string) { + function writeFile(host: fakes.FakeServerHost, path: string, content: string) { host.vfs.writeFile(path, content); } - function writeConfigFile(host: mocks.MockServerHost, path: string, config: any = {}) { + function writeConfigFile(host: fakes.FakeServerHost, path: string, config: any = {}) { const compilerOptions = (config.compilerOptions || (config.compilerOptions = {})); compilerOptions.listEmittedFiles = true; writeFile(host, path, JSON.stringify(config)); } - function waitAndCheckAffectedFiles(host: mocks.MockServerHost, affectedFiles: ReadonlyArray, unaffectedFiles?: ReadonlyArray) { + function waitAndCheckAffectedFiles(host: fakes.FakeServerHost, affectedFiles: ReadonlyArray, unaffectedFiles?: ReadonlyArray) { host.checkTimeoutQueueLengthAndRun(1); checkAffectedFiles(host, affectedFiles, unaffectedFiles); } - function checkAffectedFiles(host: mocks.MockServerHost, affectedFiles: ReadonlyArray, unaffectedFiles?: ReadonlyArray) { + function checkAffectedFiles(host: fakes.FakeServerHost, affectedFiles: ReadonlyArray, unaffectedFiles?: ReadonlyArray) { affectedFiles = getEmittedLines(affectedFiles, host, formatOutputFile); checkOutputContains(host, affectedFiles); if (unaffectedFiles) { @@ -788,13 +785,13 @@ namespace ts.tscWatch { const file2OutputPath = "/a/b.js"; const commonOutputPaths: ReadonlyArray = [file1OutputPath, file2OutputPath]; - function writeCommonFiles(host: mocks.MockServerHost) { + function writeCommonFiles(host: fakes.FakeServerHost) { writeFile(host, file1Path, `let x = 1`); writeFile(host, file2Path, `let y = 1`); } it("if neither is set", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath); @@ -806,7 +803,7 @@ namespace ts.tscWatch { }); it("if --out is set", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath, { compilerOptions: { out: "/a/out.js" } }); @@ -818,7 +815,7 @@ namespace ts.tscWatch { }); it("if --outFile is set", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath, { compilerOptions: { outFile: "/a/out.js" } }); @@ -851,7 +848,7 @@ namespace ts.tscWatch { globalFile3OutputPath ]; - function writeCommonFiles(host: mocks.MockServerHost, files?: string[]) { + function writeCommonFiles(host: fakes.FakeServerHost, files?: string[]) { if (!files || ts.contains(files, moduleFile1Path)) { writeFile(host, moduleFile1Path, `export function Foo() { };`); } @@ -870,7 +867,7 @@ namespace ts.tscWatch { } it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath); @@ -887,7 +884,7 @@ namespace ts.tscWatch { }); it("should be up-to-date with the reference map changes", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath); @@ -917,7 +914,7 @@ namespace ts.tscWatch { }); it("should be up-to-date with deleted files", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath); @@ -933,7 +930,7 @@ namespace ts.tscWatch { }); it("should be up-to-date with newly created files", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath); @@ -946,7 +943,7 @@ namespace ts.tscWatch { }); it("should detect changes in non-root files", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host, [file1Consumer1Path, moduleFile1Path]); writeConfigFile(host, configFilePath, { files: [file1Consumer1Path] }); @@ -963,7 +960,7 @@ namespace ts.tscWatch { }); it("should return all files if a global file changed shape", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath); @@ -976,7 +973,7 @@ namespace ts.tscWatch { }); it("should always return the file itself if '--isolatedModules' is specified", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath, { compilerOptions: { isolatedModules: true } }); @@ -989,7 +986,7 @@ namespace ts.tscWatch { }); it("should always return the file itself if '--out' or '--outFile' is specified", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath, { compilerOptions: { module: "system", outFile: "/a/b/out.js" } }); @@ -1001,7 +998,7 @@ namespace ts.tscWatch { }); it("should return cascaded affected file list", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeCommonFiles(host); writeConfigFile(host, configFilePath); @@ -1009,7 +1006,7 @@ namespace ts.tscWatch { checkAffectedFiles(host, commonOutputPaths); writeFile(host, "/a/b/file1Consumer1Consumer1.ts", `import {y} from "./file1Consumer1";`); - writeFile(host, file1Consumer1Path, `import {Foo} from "./moduleFile1"; export var y = 10; export var T: number;`) + writeFile(host, file1Consumer1Path, `import {Foo} from "./moduleFile1"; export var y = 10; export var T: number;`); waitAndCheckAffectedFiles(host, [file1Consumer1OutputPath, "/a/b/file1Consumer1Consumer1.js"], commonOutputPaths); // Doesn't change the shape of file1Consumer1 @@ -1017,14 +1014,14 @@ namespace ts.tscWatch { waitAndCheckAffectedFiles(host, [moduleFile1OutputPath, file1Consumer1OutputPath, file1Consumer2OutputPath], commonOutputPaths); // Change both files before the timeout - writeFile(host, file1Consumer1Path, `import {Foo} from "./moduleFile1"; export var y = 10; export var T: number; export var T2: number;`) + writeFile(host, file1Consumer1Path, `import {Foo} from "./moduleFile1"; export var y = 10; export var T: number; export var T2: number;`); writeFile(host, moduleFile1Path, `export var T2: number;export function Foo() { };`); waitAndCheckAffectedFiles(host, [moduleFile1OutputPath, file1Consumer1OutputPath, file1Consumer2OutputPath, "/a/b/file1Consumer1Consumer1.js"], commonOutputPaths); }); it("should work fine for files with circular references", () => { // TODO: do not exit on such errors? Just continue to watch the files for update in watch mode - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeFile(host, "/a/b/file1.ts", `/// \nexport var t1 = 10;`); writeFile(host, "/a/b/file2.ts", `/// \nexport var t2 = 10;`); writeConfigFile(host, configFilePath); @@ -1037,7 +1034,7 @@ namespace ts.tscWatch { }); it("should detect removed code file", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeFile(host, "/a/b/referenceFile1.ts", `/// \nexport var x = Foo();`); writeCommonFiles(host, [moduleFile1Path]); writeConfigFile(host, configFilePath); @@ -1050,7 +1047,7 @@ namespace ts.tscWatch { }); it("should detect non-existing code file", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); writeFile(host, "/a/b/referenceFile1.ts", `/// \nexport var x = Foo();`); writeConfigFile(host, configFilePath); @@ -1071,7 +1068,7 @@ namespace ts.tscWatch { { title: "\\r\\n", args: ["\r\n"] }, { title: "\\n", args: ["\n"] } ], (newLine: "\r\n" | "\n") => { - const host = new mocks.MockServerHost({ newLine }); + const host = new fakes.FakeServerHost({ newLine }); writeFile(host, "/a/app.ts", `var x = 1;${newLine}var y = 2;`); createWatchModeWithoutConfigFile(["/a/app.ts"], host, { listEmittedFiles: true }); @@ -1088,80 +1085,64 @@ namespace ts.tscWatch { }); it("should emit specified file", () => { - const filesWritten = new Set(); - const host = new class extends mocks.MockServerHost { - writeFile(path: string, content: string) { - filesWritten.add(path); - super.writeFile(path, content); - } - }({ newLine: "\r\n" }); + const host = new fakes.FakeServerHost({ newLine: "\r\n" }); writeFile(host, "/a/b/f1.ts", `export function Foo() { return 10; }`); writeFile(host, "/a/b/f2.ts", `import {Foo} from "./f1"; export let y = Foo();`); writeFile(host, "/a/b/f3.ts", `import {y} from "./f2"; let x = y;`); writeConfigFile(host, "/a/b/tsconfig.json"); + const writeFileSpy1 = spy(host, "writeFile"); + createWatchModeWithConfigFile("/a/b/tsconfig.json", host); checkAffectedFiles(host, ["/a/b/f1.js", "/a/b/f2.js", "/a/b/f3.js"]); - assert.isTrue(filesWritten.has("/a/b/f1.js")); - assert.strictEqual(host.readFile("/a/b/f1.js"), `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`); + writeFileSpy1 + .verify(_ => _("/a/b/f1.js", `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`, Arg.any()), Times.once()) + .verify(_ => _("/a/b/f2.js", `"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`, Arg.any()), Times.once()) + .verify(_ => _("/a/b/f3.js", `"use strict";\r\nexports.__esModule = true;\r\nvar f2_1 = require("./f2");\r\nvar x = f2_1.y;\r\n`, Arg.any()), Times.once()) + .revoke(); - assert.isTrue(filesWritten.has("/a/b/f2.js")); - assert.strictEqual(host.readFile("/a/b/f2.js"), `"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`); - - assert.isTrue(filesWritten.has("/a/b/f3.js")); - assert.strictEqual(host.readFile("/a/b/f3.js"), `"use strict";\r\nexports.__esModule = true;\r\nvar f2_1 = require("./f2");\r\nvar x = f2_1.y;\r\n`); - - filesWritten.clear(); + const writeFileSpy2 = spy(host, "writeFile"); writeFile(host, "/a/b/f1.ts", `export function Foo() { return 10; }export function foo2() { return 2; }`); waitAndCheckAffectedFiles(host, ["/a/b/f1.js", "/a/b/f2.js"], ["/a/b/f3.js"]); - assert.isTrue(filesWritten.has("/a/b/f1.js")); - assert.strictEqual(host.readFile("/a/b/f1.js"), `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\nfunction foo2() { return 2; }\r\nexports.foo2 = foo2;\r\n`); - - assert.isTrue(filesWritten.has("/a/b/f2.js")); - assert.strictEqual(host.readFile("/a/b/f2.js"), `"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`); - - assert.isFalse(filesWritten.has("/a/b/f3.js")); + writeFileSpy2 + .verify(_ => _("/a/b/f1.js", `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\nfunction foo2() { return 2; }\r\nexports.foo2 = foo2;\r\n`, Arg.any()), Times.once()) + .verify(_ => _("/a/b/f2.js", `"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`, Arg.any()), Times.once()) + .verify(_ => _("/a/b/f3.js", Arg.string(), Arg.any()), Times.none()) + .revoke(); }); it("Elides const enums correctly in incremental compilation", () => { - const filesWritten = new Set(); - const host = new class extends mocks.MockServerHost { - writeFile(path: string, content: string) { - filesWritten.add(path); - super.writeFile(path, content); - } - }({ lib: true, newLine: "\n" }); + const host = new fakes.FakeServerHost({ lib: true, newLine: "\n" }); writeFile(host, "/user/someone/projects/myproject/file1.ts", `export const enum E1 { V = 1 }`); writeFile(host, "/user/someone/projects/myproject/file2.ts", `import { E1 } from "./file1"; export const enum E2 { V = E1.V }`); writeFile(host, "/user/someone/projects/myproject/file3.ts", `import { E2 } from "./file2"; const v: E2 = E2.V;`); + const writeFileSpy1 = spy(host, "writeFile"); + createWatchModeWithoutConfigFile(["/user/someone/projects/myproject/file1.ts", "/user/someone/projects/myproject/file2.ts", "/user/someone/projects/myproject/file3.ts"], host, { listEmittedFiles: true }); checkAffectedFiles(host, ["/user/someone/projects/myproject/file1.js", "/user/someone/projects/myproject/file2.js", "/user/someone/projects/myproject/file3.js"]); - assert.isTrue(filesWritten.has("/user/someone/projects/myproject/file1.js")); - assert.strictEqual(host.readFile("/user/someone/projects/myproject/file1.js"), `"use strict";\nexports.__esModule = true;\n`); + writeFileSpy1 + .verify(_ => _("/user/someone/projects/myproject/file1.js", `"use strict";\nexports.__esModule = true;\n`, Arg.any()), Times.once()) + .verify(_ => _("/user/someone/projects/myproject/file2.js", `"use strict";\nexports.__esModule = true;\n`, Arg.any()), Times.once()) + .verify(_ => _("/user/someone/projects/myproject/file3.js", `"use strict";\nexports.__esModule = true;\nvar v = 1 /* V */;\n`, Arg.any()), Times.once()) + .revoke(); - assert.isTrue(filesWritten.has("/user/someone/projects/myproject/file2.js")); - assert.strictEqual(host.readFile("/user/someone/projects/myproject/file2.js"), `"use strict";\nexports.__esModule = true;\n`); - - assert.isTrue(filesWritten.has("/user/someone/projects/myproject/file3.js")); - assert.strictEqual(host.readFile("/user/someone/projects/myproject/file3.js"), `"use strict";\nexports.__esModule = true;\nvar v = 1 /* V */;\n`); - - filesWritten.clear(); + const writeFileSpy2 = spy(host, "writeFile"); writeFile(host, "/user/someone/projects/myproject/file1.ts", `export const enum E1 { V = 1 }function foo2() { return 2; }`); waitAndCheckAffectedFiles(host, ["/user/someone/projects/myproject/file1.js"], ["/user/someone/projects/myproject/file2.js", "/user/someone/projects/myproject/file3.js"]); - assert.isTrue(filesWritten.has("/user/someone/projects/myproject/file1.js")); - assert.strictEqual(host.readFile("/user/someone/projects/myproject/file1.js"), `"use strict";\nexports.__esModule = true;\nfunction foo2() { return 2; }\n`); - - assert.isFalse(filesWritten.has("/user/someone/projects/myproject/file2.js")); - assert.isFalse(filesWritten.has("/user/someone/projects/myproject/file3.js")); + writeFileSpy2 + .verify(_ => _("/user/someone/projects/myproject/file1.js", `"use strict";\nexports.__esModule = true;\nfunction foo2() { return 2; }\n`, Arg.any()), Times.once()) + .verify(_ => _("/user/someone/projects/myproject/file2.js", Arg.string(), Arg.any()), Times.none()) + .verify(_ => _("/user/someone/projects/myproject/file3.js", Arg.string(), Arg.any()), Times.none()) + .revoke(); }); }); }); @@ -1171,11 +1152,10 @@ namespace ts.tscWatch { const rootContent1 = `import {x} from "f1"`; const importedContent = `foo()`; - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/d/f0.ts", rootContent1); host.vfs.addFile("/a/f1.ts", importedContent); - const fileExists = host.fileExists; const watch = createWatchModeWithoutConfigFile(["/a/d/f0.ts"], host, { module: ModuleKind.AMD }); // ensure that imported file was found @@ -1185,8 +1165,7 @@ namespace ts.tscWatch { ], /*isInitial*/ true); // spy on calls to fileExists to make sure that disk is not touched - const fileExistsSpy1 = new Spy(fileExists); - host.fileExists = fileExistsSpy1.value; + const fileExistsSpy1 = spy(host, "fileExists"); // write file and trigger synchronization const rootContent2 = `import {x} from "f1"\nvar x: string = 1;`; @@ -1194,7 +1173,9 @@ namespace ts.tscWatch { host.runQueuedTimeoutCallbacks(); // verify fileExists was not called. - fileExistsSpy1.verify(_ => _(Arg.any()), Times.none()); + fileExistsSpy1 + .verify(_ => _(Arg.any()), Times.none()) + .revoke(); // ensure file has correct number of errors after edit checkOutputErrors(host, [ @@ -1204,8 +1185,7 @@ namespace ts.tscWatch { ]); // spy on calls to fileExists to make sure LSHost only searches for 'f2' - const fileExistsSpy2 = new Spy(fileExists); - host.fileExists = fileExistsSpy2.value; + const fileExistsSpy2 = spy(host, "fileExists"); // write file and trigger synchronization const rootContent3 = `import {x} from "f2"`; @@ -1215,7 +1195,8 @@ namespace ts.tscWatch { // verify fileExists was called correctly fileExistsSpy2 .verify(_ => _(Arg.includes("/f2.")), Times.atLeastOnce()) - .verify(_ => _(Arg.not(Arg.includes("/f2."))), Times.none()); + .verify(_ => _(Arg.not(Arg.includes("/f2."))), Times.none()) + .revoke(); // ensure file has correct number of errors after edit checkOutputErrors(host, [ @@ -1223,8 +1204,7 @@ namespace ts.tscWatch { ]); // spy on calls to fileExists to make sure LSHost only searches for 'f1' - const fileExistsSpy3 = new Spy(fileExists); - host.fileExists = fileExistsSpy3.value; + const fileExistsSpy3 = spy(host, "fileExists"); // write file and trigger synchronization const rootContent4 = `import {x} from "f1"`; @@ -1234,7 +1214,8 @@ namespace ts.tscWatch { // verify fileExists was called correctly fileExistsSpy3 .verify(_ => _(Arg.includes("/f1.")), Times.atLeastOnce()) - .verify(_ => _(Arg.not(Arg.includes("/f1."))), Times.none()); + .verify(_ => _(Arg.not(Arg.includes("/f1."))), Times.none()) + .revoke(); checkOutputErrors(host, [ createFileIsNotAModuleDiagnostic(watch(), "/a/d/f0.ts", rootContent1, "f1", "/a/f1.ts"), @@ -1245,34 +1226,34 @@ namespace ts.tscWatch { it("loads missing files from disk", () => { const rootContent1 = `import {x} from "bar"`; - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/foo.ts", rootContent1); - const fileExists = host.fileExists; - // spy on calls to fileExists when starting watch mode - const fileExistsSpy1 = new Spy(fileExists); - host.fileExists = fileExistsSpy1.value; + const fileExistsSpy1 = spy(host, "fileExists"); const watch = createWatchModeWithoutConfigFile(["/a/foo.ts"], host, { module: ModuleKind.AMD }); // verify fileExists was called correctly - fileExistsSpy1.verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()); + fileExistsSpy1 + .verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()) + .revoke(); checkOutputErrors(host, [ createCannotFindModuleDiagnostic(watch(), "/a/foo.ts", rootContent1, "bar") ], /*isInitial*/ true); // spy on calls to fileExists after synchronization is triggered - const fileExistsSpy2 = new Spy(fileExists); - host.fileExists = fileExistsSpy2.value; + const fileExistsSpy2 = spy(host, "fileExists"); host.vfs.writeFile("/a/foo.ts", `import {y} from "bar"`); host.vfs.writeFile("/a/bar.d.ts", `export const y = 1;`); host.runQueuedTimeoutCallbacks(); // verify fileExists was called correctly - fileExistsSpy2.verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()); + fileExistsSpy2 + .verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()) + .revoke(); checkOutputErrors(host, emptyArray); }); @@ -1281,53 +1262,54 @@ namespace ts.tscWatch { const rootContent = `import {x} from "bar"`; const importedContent = `export const y = 1;export const x = 10;`; - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/foo.ts", rootContent); host.vfs.addFile("/a/bar.d.ts", importedContent); - const fileExists = host.fileExists; - // spy on fileExists when starting watch mode - const fileExistsSpy1 = new Spy(fileExists); - host.fileExists = fileExistsSpy1.value; + const fileExistsSpy1 = spy(host, "fileExists"); const watch = createWatchModeWithoutConfigFile(["/a/foo.ts"], host, { module: ModuleKind.AMD }); // verify fileExists was called correctly - fileExistsSpy1.verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()); + fileExistsSpy1 + .verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()) + .revoke(); checkOutputErrors(host, emptyArray, /*isInitial*/ true); // spy on fileExists when triggering synchronization - const fileExistsSpy2 = new Spy(fileExists); - host.fileExists = fileExistsSpy2.value; + const fileExistsSpy2 = spy(host, "fileExists"); host.vfs.removeFile("/a/bar.d.ts"); host.runQueuedTimeoutCallbacks(); // verify fileExists was called correctly - fileExistsSpy2.verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()); + fileExistsSpy2 + .verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()) + .revoke(); checkOutputErrors(host, [ createCannotFindModuleDiagnostic(watch(), "/a/foo.ts", rootContent, "bar") ]); // spy on fileExists when triggering synchronization - const fileExistsSpy3 = new Spy(fileExists); - host.fileExists = fileExistsSpy3.value; + const fileExistsSpy3 = spy(host, "fileExists"); - host.vfs.writeFile("/a/bar.d.ts", importedContent);; + host.vfs.writeFile("/a/bar.d.ts", importedContent); host.checkTimeoutQueueLengthAndRun(1); // verify fileExists was called correctly. - fileExistsSpy3.verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()); + fileExistsSpy3 + .verify(_ => _(Arg.includes("/bar.")), Times.atLeastOnce()) + .revoke(); checkOutputErrors(host, emptyArray); }); it("works when module resolution changes to ambient module", () => { const rootContent = `import * as fs from "fs";`; - const host = new mocks.MockServerHost({ vfs: { currentDirectory: "/a/b" }, lib: true }); + const host = new fakes.FakeServerHost({ vfs: { currentDirectory: "/a/b" }, lib: true }); host.vfs.addFile("/a/b/foo.ts", rootContent); const watch = createWatchModeWithoutConfigFile(["/a/b/foo.ts"], host, { }); @@ -1346,7 +1328,7 @@ namespace ts.tscWatch { const rootContent = `import * as fs from "fs";\nimport * as u from "url";`; const fileContent1 = `declare module "url" {\n export interface Url {\n href?: string;\n }\n}`; - const host = new mocks.MockServerHost({ vfs: { currentDirectory: "/a/b" }, lib: true }); + const host = new fakes.FakeServerHost({ vfs: { currentDirectory: "/a/b" }, lib: true }); host.vfs.addFile("/a/b/foo.ts", rootContent); host.vfs.addFile("/a/b/bar.d.ts", fileContent1); @@ -1368,7 +1350,7 @@ namespace ts.tscWatch { const file2Content = `import module11 = require("module1");\nmodule11("hello");`; const file2Output = `"use strict";\nexports.__esModule = true;\nvar module11 = require("module1");\nmodule11("hello");\n`; - const host = new mocks.MockServerHost({ vfs: { currentDirectory: "/a/b/projects/myProject/" }, lib: true }); + const host = new fakes.FakeServerHost({ vfs: { currentDirectory: "/a/b/projects/myProject/" }, lib: true }); host.vfs.addFile("/a/b/projects/myProject/src/file1.ts", file1Content); host.vfs.addFile("/a/b/projects/myProject/src/file2.ts", file2Content); host.vfs.addFile("/a/b/projects/myProject/node_modules/module1/index.js", `module.exports = options => { return options.toString(); }`); @@ -1382,18 +1364,15 @@ namespace ts.tscWatch { } })); - const writeFile = host.writeFile; - // spy on calls to writeFile when starting watch mode - const writeFileSpy1 = new Spy(writeFile); - host.writeFile = writeFileSpy1.value; + const writeFileSpy1 = spy(host, "writeFile"); const watch = createWatchModeWithConfigFile("/a/b/projects/myProject/src/tsconfig.json", host); checkProgramActualFiles(watch(), [ "/a/b/projects/myProject/src/file1.ts", "/a/b/projects/myProject/src/file2.ts", "/a/b/projects/myProject/node_modules/module1/index.js", - mocks.MockServerHost.libPath + fakes.FakeServerHost.libPath ]); checkOutputErrors(host, emptyArray, /*isInitial*/ true); @@ -1401,16 +1380,11 @@ namespace ts.tscWatch { writeFileSpy1 .verify(_ => _("/a/b/projects/myProject/dist/file1.js", file1Output, Arg.any()), Times.once()) .verify(_ => _("/a/b/projects/myProject/dist/file2.js", file2Output, Arg.any()), Times.once()) - .verify(_ => _("/a/b/projects/myProject/dist/index.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/src/index.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/src/file1.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/src/file2.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/lib.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/lib.d.ts", Arg.string(), Arg.any()), Times.none()); + .verify(_ => _(Arg.nor("/a/b/projects/myProject/dist/file1.js", "/a/b/projects/myProject/dist/file2.js"), Arg.string(), Arg.any()), Times.none()) + .revoke(); // spy on calls to writeFile when triggering synchronization - const writeFileSpy2 = new Spy(writeFile); - host.writeFile = writeFileSpy2.value; + const writeFileSpy2 = spy(host, "writeFile"); host.vfs.writeFile("/a/b/projects/myProject/src/file1.ts", file1Content + "\n;"); host.runQueuedTimeoutCallbacks(); @@ -1418,26 +1392,21 @@ namespace ts.tscWatch { "/a/b/projects/myProject/src/file1.ts", "/a/b/projects/myProject/src/file2.ts", "/a/b/projects/myProject/node_modules/module1/index.js", - mocks.MockServerHost.libPath + fakes.FakeServerHost.libPath ]); checkOutputErrors(host, emptyArray); // verify writeFile was called correctly writeFileSpy2 .verify(_ => _("/a/b/projects/myProject/dist/file1.js", file1Output + ";\n", Arg.any()), Times.once()) - .verify(_ => _("/a/b/projects/myProject/dist/file2.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/index.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/src/index.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/src/file1.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/src/file2.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/lib.js", Arg.string(), Arg.any()), Times.none()) - .verify(_ => _("/a/b/projects/myProject/dist/lib.d.ts", Arg.string(), Arg.any()), Times.none()); + .verify(_ => _(Arg.not("/a/b/projects/myProject/dist/file1.js"), Arg.string(), Arg.any()), Times.none()) + .revoke(); }); }); describe("with when module emit is specified as node", () => { it("when instead of filechanged recursive directory watcher is invoked", () => { - const host = new mocks.MockServerHost({ lib: true }); + const host = new fakes.FakeServerHost({ lib: true }); host.vfs.addFile("/a/rootFolder/project/Scripts/TypeScript.ts", `var z = 10;`); host.vfs.addFile("/a/rootFolder/project/Scripts/Javascript.js", `var zz = 10;`); host.vfs.addFile("/a/rootFolder/project/tsconfig.json", JSON.stringify({ @@ -1451,13 +1420,12 @@ namespace ts.tscWatch { ], })); - const writeFile = host.writeFile; const watch = createWatchModeWithConfigFile("/a/rootFolder/project/tsconfig.json", host); checkProgramActualFiles(watch(), [ "/a/rootFolder/project/Scripts/TypeScript.ts", "/a/rootFolder/project/Scripts/Javascript.js", - mocks.MockServerHost.libPath + fakes.FakeServerHost.libPath ]); @@ -1465,8 +1433,7 @@ namespace ts.tscWatch { host.vfs.removeFile("/a/rootFolder/project/Scripts/TypeScript.ts"); host.runQueuedTimeoutCallbacks(); - const writeFileSpy1 = new Spy(writeFile); - host.writeFile = writeFileSpy1.value; + const writeFileSpy1 = spy(host, "writeFile"); host.vfs.writeFile("/a/rootFolder/project/Scripts/TypeScript.ts", `var zz30 = 100;`); host.runQueuedTimeoutCallbacks(); @@ -1474,10 +1441,12 @@ namespace ts.tscWatch { checkProgramActualFiles(watch(), [ "/a/rootFolder/project/Scripts/TypeScript.ts", "/a/rootFolder/project/Scripts/Javascript.js", - mocks.MockServerHost.libPath + fakes.FakeServerHost.libPath ]); - writeFileSpy1.verify(_ => _("/a/rootFolder/project/Static/scripts/TypeScript.js", `var zz30 = 100;\n`, Arg.any()), Times.once()); + writeFileSpy1 + .verify(_ => _("/a/rootFolder/project/Static/scripts/TypeScript.js", `var zz30 = 100;\n`, Arg.any()), Times.once()) + .revoke(); }); }); }); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 0ef61c1317d..35358e14ad4 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -99,7 +99,7 @@ namespace ts.projectSystem { this.addPostExecAction("success", cb); } - sendResponse(response: server.SetTypings | server.InvalidateCachedTypings) { + sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) { this.projectService.updateTypingsForProject(response); } @@ -5583,7 +5583,7 @@ namespace ts.projectSystem { content: "export class Cookie { }" }; const es2016LibFile: FileOrFolder = { - path: "/a/lib/lib.es2016.full.d.ts", + path: "/.ts/lib.es2016.full.d.ts", content: libFile.content }; const typeRoots = ["types", "node_modules/@types"]; diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 2d9a86607fa..82e22e01ce4 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -3,9 +3,13 @@ /// /// /// -/// +/// namespace ts.projectSystem { + import spy = typemock.spy; + import Arg = typemock.Arg; + import Times = typemock.Times; + import TI = server.typingsInstaller; import validatePackageName = JsTyping.validatePackageName; import PackageNameValidationResult = JsTyping.PackageNameValidationResult; @@ -40,19 +44,14 @@ namespace ts.projectSystem { } } - function executeCommand(self: Installer, host: TestServerHost | mocks.MockServerHost, installedTypings: string[] | string, typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void { + function executeCommand(self: Installer, host: fakes.FakeServerHost, installedTypings: string[] | string, typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void { self.addPostExecAction(installedTypings, success => { for (const file of typingFiles) { - if (host instanceof mocks.MockServerHost) { - if (typeof file.content === "string") { - host.vfs.addFile(file.path, file.content, { overwrite: true }); - } - else { - host.vfs.addDirectory(file.path); - } + if (typeof file.content === "string") { + host.vfs.addFile(file.path, file.content, { overwrite: true }); } else { - host.ensureFileOrFolder(file); + host.vfs.addDirectory(file.path); } } cb(success); @@ -75,27 +74,24 @@ namespace ts.projectSystem { describe("local module", () => { it("should not be picked up", () => { - const host = new mocks.MockServerHost(); + const host = new fakes.FakeServerHost(); const f1 = host.vfs.addFile("/a/app.js", `const c = require('./config');`); const f2 = host.vfs.addFile("/a/config.js", `export let x = 1`); const config = host.vfs.addFile("/a/jsconfig.json", `{ "typeAcquisition": { "enable": true }, "compilerOptions": { "moduleResolution": "commonjs } }`); host.vfs.addFile("/cache/node_modules/@types/config/index.d.ts", `export let y: number;`); - const installer = new class extends Installer { - constructor() { - super(host, { typesRegistry: createTypesRegistry("config"), globalTypingsCacheLocation: "/cache" }); - } - - installWorker(_requestId: number, _args: string[], _cwd: string, _cb: TI.RequestCompletedAction) { - assert(false, "should not be called"); - } - }; + const installer = new Installer(host, { typesRegistry: createTypesRegistry("config"), globalTypingsCacheLocation: "/cache" }); + const installWorkerSpy = spy(installer, "installWorker"); const service = createProjectService(host, { typingsInstaller: installer }); service.openClientFile(f1.path); service.checkNumberOfProjects({ configuredProjects: 1 }); checkProjectActualFiles(configuredProjectAt(service, 0), [f1.path, f2.path, config.path]); installer.installAll(0); + + installWorkerSpy + .verify(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), Times.none()) + .revoke(); }); }); @@ -106,22 +102,20 @@ namespace ts.projectSystem { content: "declare const $: { x: number }" }; - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file1 = host.vfs.addFile("/a/b/app.js", ``); const tsconfig = host.vfs.addFile("/a/b/tsconfig.json", `{ "compilerOptions": { "allowJs": true }, "typeAcquisition": { "enable": true } }`); host.vfs.addFile("/a/b/package.json", `{ "name": "test", "dependencies": { "jquery": "^3.1.0" } }`); - const installer = new class extends Installer { - constructor() { - super(host, { typesRegistry: createTypesRegistry("jquery") }); - } - - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - const installedTypings = ["@types/jquery"]; - const typingFiles = [jquery]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { typesRegistry: createTypesRegistry("jquery") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + const installedTypings = ["@types/jquery"]; + const typingFiles = [jquery]; + executeCommand(installer, host, installedTypings, typingFiles, cb); + } + }); const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer }); projectService.openClientFile(file1.path); @@ -135,6 +129,8 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [file1.path, jquery.path, tsconfig.path]); + + installWorkerSpy.revoke(); }); it("inferred project (typings installed)", () => { @@ -143,21 +139,19 @@ namespace ts.projectSystem { content: "declare const $: { x: number }" }; - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file1 = host.vfs.addFile("/a/b/app.js", ``); host.vfs.addFile("/a/b/package.json", `{ "name": "test", "dependencies": { "jquery": "^3.1.0" } }`); - const installer = new class extends Installer { - constructor() { - super(host, { typesRegistry: createTypesRegistry("jquery") }); - } - - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - const installedTypings = ["@types/jquery"]; - const typingFiles = [jquery]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { typesRegistry: createTypesRegistry("jquery") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + const installedTypings = ["@types/jquery"]; + const typingFiles = [jquery]; + executeCommand(installer, host, installedTypings, typingFiles, cb); + } + }); const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer }); projectService.openClientFile(file1.path); @@ -170,21 +164,16 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(p, [file1.path, jquery.path]); + + installWorkerSpy.revoke(); }); it("external project - no type acquisition, no .d.ts/js files", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file1 = host.vfs.addFile("/a/b/app.ts", ``); - const installer = new class extends Installer { - constructor() { - super(host); - } - - enqueueInstallTypingsRequest() { - assert(false, "auto discovery should not be enabled"); - } - }; + const installer = new Installer(host); + const enqueueInstallTypingsRequestSpy = spy(installer, "enqueueInstallTypingsRequest"); const projectFileName = "/a/app/test.csproj"; const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -198,19 +187,18 @@ namespace ts.projectSystem { // by default auto discovery will kick in if project contain only .js/.d.ts files // in this case project contain only ts files - no auto discovery projectService.checkNumberOfProjects({ externalProjects: 1 }); + + enqueueInstallTypingsRequestSpy + .verify(_ => _(Arg.any(), Arg.any(), Arg.any()), Times.none(), "auto discovery should not be enabled") + .revoke(); }); it("external project - no auto in typing acquisition, no .d.ts/js files", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file1 = host.vfs.addFile("/a/b/app.ts", ``); - const installer = new class extends Installer { - constructor() { - super(host, { typesRegistry: createTypesRegistry("jquery") }); - } - enqueueInstallTypingsRequest() { - assert(false, "auto discovery should not be enabled"); - } - }; + + const installer = new Installer(host, { typesRegistry: createTypesRegistry("jquery") }); + const enqueueInstallTypingsRequestSpy = spy(installer, "enqueueInstallTypingsRequest"); const projectFileName = "/a/app/test.csproj"; const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -224,10 +212,14 @@ namespace ts.projectSystem { // by default auto discovery will kick in if project contain only .js/.d.ts files // in this case project contain only ts files - no auto discovery even if type acquisition is set projectService.checkNumberOfProjects({ externalProjects: 1 }); + + enqueueInstallTypingsRequestSpy + .verify(_ => _(Arg.any(), Arg.any(), Arg.any()), Times.none(), "auto discovery should not be enabled") + .revoke(); }); it("external project - autoDiscovery = true, no .d.ts/js files", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file1 = host.vfs.addFile("/a/b/app.ts", ``); const jquery = { @@ -235,21 +227,16 @@ namespace ts.projectSystem { content: "declare const $: { x: number }" }; - let enqueueIsCalled = false; - const installer = new class extends Installer { - constructor() { - super(host, { typesRegistry: createTypesRegistry("jquery") }); - } - enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: server.SortedReadonlyArray) { - enqueueIsCalled = true; - super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - const installedTypings = ["@types/node"]; - const typingFiles = [jquery]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { typesRegistry: createTypesRegistry("jquery") }); + const enqueueInstallTypingsRequestSpy = spy(installer, "enqueueInstallTypingsRequest"); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + const installedTypings = ["@types/jquery"]; + const typingFiles = [jquery]; + executeCommand(installer, host, installedTypings, typingFiles, cb); + } + }); const projectFileName = "/a/app/test.csproj"; const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -260,11 +247,16 @@ namespace ts.projectSystem { typeAcquisition: { enable: true, include: ["jquery"] } }); - assert.isTrue(enqueueIsCalled, "expected enqueueIsCalled to be true"); installer.installAll(/*expectedCount*/ 1); // auto is set in type acquisition - use it even if project contains only .ts files projectService.checkNumberOfProjects({ externalProjects: 1 }); + + enqueueInstallTypingsRequestSpy + .verify(_ => _(Arg.any(), Arg.any(), Arg.any()), Times.atLeastOnce()) + .revoke(); + + installWorkerSpy.revoke(); }); it("external project - no type acquisition, with only js, jsx, d.ts files", () => { @@ -272,7 +264,7 @@ namespace ts.projectSystem { // 1. react typings are installed for .jsx // 2. loose files names are matched against safe list for typings if // this is a JS project (only js, jsx, d.ts files are present) - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file1 = host.vfs.addFile("/a/b/lodash.js", ``); const file2 = host.vfs.addFile("/a/b/file2.jsx", ``); const file3 = host.vfs.addFile("/a/b/file3.d.ts", ``); @@ -287,16 +279,15 @@ namespace ts.projectSystem { content: "declare const lodash: { x: number }" }; - const installer = new class extends Installer { - constructor() { - super(host, { typesRegistry: createTypesRegistry("lodash", "react") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - const installedTypings = ["@types/lodash", "@types/react"]; - const typingFiles = [lodash, react]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { typesRegistry: createTypesRegistry("lodash", "react") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + const installedTypings = ["@types/lodash", "@types/react"]; + const typingFiles = [lodash, react]; + executeCommand(installer, host, installedTypings, typingFiles, cb); + } + }); const projectFileName = "/a/app/test.csproj"; const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -317,28 +308,24 @@ namespace ts.projectSystem { host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]); + + installWorkerSpy.revoke(); }); it("external project - no type acquisition, with js & ts files", () => { // Tests: // 1. No typings are included for JS projects when the project contains ts files - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file1 = host.vfs.addFile("/a/b/jquery.js", ``); const file2 = host.vfs.addFile("/a/b/file2.ts", ``); - const installer = new class extends Installer { - constructor() { - super(host, { typesRegistry: createTypesRegistry("jquery") }); - } - enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: server.SortedReadonlyArray) { - super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - const installedTypings: string[] = []; - const typingFiles: FileOrFolder[] = []; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { typesRegistry: createTypesRegistry("jquery") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, [], [], cb); + } + }); const projectFileName = "/a/app/test.csproj"; const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -357,6 +344,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file2.path]); + installWorkerSpy.revoke(); }); it("external project - with type acquisition, with only js, d.ts files", () => { @@ -364,7 +352,7 @@ namespace ts.projectSystem { // 1. Safelist matching, type acquisition includes/excludes and package.json typings are all acquired // 2. Types for safelist matches are not included when they also appear in the type acquisition exclude list // 3. Multiple includes and excludes are respected in type acquisition - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file1 = host.vfs.addFile("/a/b/lodash.js", ``); const file2 = host.vfs.addFile("/a/b/commander.js", ``); const file3 = host.vfs.addFile("/a/b/file3.d.ts", ``); @@ -388,16 +376,15 @@ namespace ts.projectSystem { content: "declare const moment: { x: number }" }; - const installer = new class extends Installer { - constructor() { - super(host, { typesRegistry: createTypesRegistry("jquery", "commander", "moment", "express") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment"]; - const typingFiles = [commander, express, jquery, moment]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { typesRegistry: createTypesRegistry("jquery", "commander", "moment", "express") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment"]; + const typingFiles = [commander, express, jquery, moment]; + executeCommand(installer, host, installedTypings, typingFiles, cb); + } + }); const projectFileName = "/a/app/test.csproj"; const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -418,10 +405,11 @@ namespace ts.projectSystem { host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]); + installWorkerSpy.revoke(); }); it("Throttle - delayed typings to install", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const lodashJs = host.vfs.addFile("/a/b/lodash.js", ``); const commanderJs = host.vfs.addFile("/a/b/commander.js", ``); const file3 = host.vfs.addFile("/a/b/file3.d.ts", ``); @@ -450,15 +438,14 @@ namespace ts.projectSystem { }; const typingFiles = [commander, express, jquery, moment, lodash]; - const installer = new class extends Installer { - constructor() { - super(host, { throttleLimit: 3, typesRegistry: createTypesRegistry("commander", "express", "jquery", "moment", "lodash") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment", "@types/lodash"]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { typesRegistry: createTypesRegistry("commander", "express", "jquery", "moment", "lodash") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment", "@types/lodash"]; + executeCommand(installer, host, installedTypings, typingFiles, cb); + } + }); const projectFileName = "/a/app/test.csproj"; const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -481,10 +468,11 @@ namespace ts.projectSystem { host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]); + installWorkerSpy.revoke(); }); it("Throttle - delayed run install requests", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const lodashJs = host.vfs.addFile("/a/b/lodash.js", ``); const commanderJs = host.vfs.addFile("/a/b/commander.js", ``); const file3 = host.vfs.addFile("/a/b/file3.d.ts", ``); @@ -521,21 +509,18 @@ namespace ts.projectSystem { typings: typingsName("gulp") }; - const installer = new class extends Installer { - constructor() { - super(host, { throttleLimit: 1, typesRegistry: createTypesRegistry("commander", "jquery", "lodash", "cordova", "gulp", "grunt") }); - } - installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - let typingFiles: (FileOrFolder & { typings: string })[] = []; - if (args.indexOf(typingsName("commander")) >= 0) { - typingFiles = [commander, jquery, lodash, cordova]; + const installer = new Installer(host, { throttleLimit: 1, typesRegistry: createTypesRegistry("commander", "jquery", "lodash", "cordova", "gulp", "grunt") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.includes(typingsName("commander")), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, [commander.typings, jquery.typings, lodash.typings, cordova.typings], [commander, jquery, lodash, cordova], cb); } - else { - typingFiles = [grunt, gulp]; + }) + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, [grunt.typings, gulp.typings], [grunt, gulp], cb); } - executeCommand(this, host, typingFiles.map(f => f.typings), typingFiles, cb); - } - }; + }); // Create project #1 with 4 typings const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -576,6 +561,7 @@ namespace ts.projectSystem { host.checkTimeoutQueueLengthAndRun(3); checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path, commander.path, jquery.path, lodash.path, cordova.path]); checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]); + installWorkerSpy.revoke(); }); it("configured projects discover from node_modules", () => { @@ -583,7 +569,7 @@ namespace ts.projectSystem { path: "/tmp/node_modules/@types/jquery/index.d.ts", content: "" }; - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const app = host.vfs.addFile("/app.js", ``); const jsconfig = host.vfs.addFile("/jsconfig.json", `{}`); host.vfs.addFile("/node_modules/jquery/index.js", ``); @@ -591,17 +577,13 @@ namespace ts.projectSystem { // Should not search deeply in node_modules. host.vfs.addFile("/node_modules/jquery/nested/package.json", `{ "name": "nested" }`); - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery", "nested") }); - } - installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - assert.deepEqual(args, [`@types/jquery@ts${versionMajorMinor}`]); - const installedTypings = ["@types/jquery"]; - const typingFiles = [jqueryDTS]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery", "nested") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.array([`@types/jquery@ts${versionMajorMinor}`]), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, ["@types/jquery"], [jqueryDTS], cb); + } + }); const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer }); projectService.openClientFile(app.path); @@ -615,10 +597,13 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); + installWorkerSpy + .verify(_ => _(Arg.any(), Arg.not(Arg.array([`@types/jquery@ts${versionMajorMinor}`])), Arg.any(), Arg.any()), Times.none()) + .revoke(); }); it("configured projects discover from bower_components", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const app = host.vfs.addFile("/app.js", ``); const jsconfig = host.vfs.addFile("/jsconfig.json", `{}`); host.vfs.addFile("/bower_components/jquery/index.js", ``); @@ -629,16 +614,13 @@ namespace ts.projectSystem { content: "" }; - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - const installedTypings = ["@types/jquery"]; - const typingFiles = [jqueryDTS]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, ["@types/jquery"], [jqueryDTS], cb); + } + }); const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer }); projectService.openClientFile(app.path); @@ -653,10 +635,11 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); + installWorkerSpy.revoke(); }); it("configured projects discover from bower.json", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const app = host.vfs.addFile("/app.js", ``); const jsconfig = host.vfs.addFile("/jsconfig.json", `{}`); host.vfs.addFile("/bower.json", `{ "dependencies": { "jquery": "^3.1.0" } }`); @@ -666,16 +649,13 @@ namespace ts.projectSystem { content: "" }; - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - const installedTypings = ["@types/jquery"]; - const typingFiles = [jqueryDTS]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, ["@types/jquery"], [jqueryDTS], cb); + } + }); const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer }); projectService.openClientFile(app.path); @@ -689,10 +669,11 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); + installWorkerSpy.revoke(); }); it("Malformed package.json should be watched", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const f = host.vfs.addFile("/a/b/app.js", `var x = 1`); host.vfs.addFile("/a/b/package.json", `{ "dependencies": { "co } }`); @@ -702,16 +683,13 @@ namespace ts.projectSystem { content: "export let x: number" }; - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - const installedTypings = ["@types/commander"]; - const typingFiles = [commander]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, ["@types/commander"], [commander], cb); + } + }); const service = createProjectService(host, { typingsInstaller: installer }); service.openClientFile(f.path); @@ -729,10 +707,11 @@ namespace ts.projectSystem { service.checkNumberOfProjects({ inferredProjects: 1 }); checkProjectActualFiles(service.inferredProjects[0], [f.path, commander.path]); + installWorkerSpy.revoke(); }); it("should install typings for unresolved imports", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const file = host.vfs.addFile("/a/b/app.js", `import * as fs from "fs";\n` + `import * as commander from "commander";`); @@ -747,16 +726,13 @@ namespace ts.projectSystem { content: "export let y: string" }; - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("node", "commander") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - const installedTypings = ["@types/node", "@types/commander"]; - const typingFiles = [node, commander]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - }; + const installer = new Installer(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("node", "commander") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, ["@types/node", "@types/commander"], [node, commander], cb); + } + }); const service = createProjectService(host, { typingsInstaller: installer }); service.openClientFile(file.path); @@ -770,10 +746,11 @@ namespace ts.projectSystem { assert.isTrue(host.fileExists(commander.path), "typings for 'commander' should be created"); checkProjectActualFiles(service.inferredProjects[0], [file.path, node.path, commander.path]); + installWorkerSpy.revoke(); }); it("should pick typing names from non-relative unresolved imports", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const f1 = host.vfs.addFile("/a/b/app.js", `import * as a from "foo/a/a";\n` + `import * as b from "foo/a/b";\n` + @@ -783,14 +760,13 @@ namespace ts.projectSystem { `import * as e from "@bar/common/apps";\n` + `import * as f from "./lib"`); - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("foo") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - executeCommand(this, host, ["foo"], [], cb); - } - }; + const installer = new Installer(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("foo") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, ["foo"], [], cb); + } + }); const projectService = createProjectService(host, { typingsInstaller: installer }); projectService.openClientFile(f1.path); @@ -805,10 +781,11 @@ namespace ts.projectSystem { ); installer.installAll(/*expectedCount*/ 1); + installWorkerSpy.revoke(); }); it("cached unresolved typings are not recomputed if program structure did not change", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const session = createSession(host); const f = { path: "/a/app.js", @@ -879,25 +856,23 @@ namespace ts.projectSystem { describe("Invalid package names", () => { it("should not be installed", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const f1 = host.vfs.addFile("/a/b/app.js", `let x = 1`); host.vfs.addFile("/a/b/package.json", `{ "dependencies": { "; say ‘Hello from TypeScript!’ #": "0.0.x" } }`); const messages: string[] = []; - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, _cb: TI.RequestCompletedAction) { - assert(false, "runCommand should not be invoked"); - } - }; + const installer = new Installer(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) }); + const installWorkerSpy = spy(installer, "installWorker"); const projectService = createProjectService(host, { typingsInstaller: installer }); projectService.openClientFile(f1.path); installer.checkPendingCommands(/*expectedCount*/ 0); assert.isTrue(messages.indexOf("Package name '; say ‘Hello from TypeScript!’ #' contains non URI safe characters") > 0, "should find package with invalid name"); + + installWorkerSpy + .verify(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), Times.none()) + .revoke(); }); }); @@ -905,7 +880,7 @@ namespace ts.projectSystem { const emptySafeList = emptyMap; it("should use mappings from safe list", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const app = host.vfs.addFile("/a/b/app.js", ``); const jquery = host.vfs.addFile("/a/b/jquery.js", ``); const chroma = host.vfs.addFile("/a/b/chroma.min.js", ``); @@ -925,7 +900,7 @@ namespace ts.projectSystem { }); it("should return node for core modules", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const f = host.vfs.addFile("/a/b/app.js", ``); const cache = createMap(); @@ -942,7 +917,7 @@ namespace ts.projectSystem { }); it("should use cached locations", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const f = host.vfs.addFile("/a/b/app.js", ``); const node = host.vfs.addFile("/a/b/node.d.ts", ``); @@ -958,7 +933,7 @@ namespace ts.projectSystem { }); it("should search only 2 levels deep", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const app = host.vfs.addFile("/app.js"); host.vfs.addFile("/node_modules/a/package.json", `{ "name": "a" }`); host.vfs.addFile("/node_modules/a/b/package.json", `{ "name": "b" }`); @@ -982,7 +957,7 @@ namespace ts.projectSystem { describe("telemetry events", () => { it("should be received", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const f1 = host.vfs.addFile("/a/app.js", ``); host.vfs.addFile("/a/package.json", `{ "dependencies": { "commander": "1.0.0" } }`); @@ -992,35 +967,30 @@ namespace ts.projectSystem { content: "export let x: number" }; - let seenTelemetryEvent = false; - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - const installedTypings = ["@types/commander"]; - const typingFiles = [commander]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) { - if (response.kind === server.EventBeginInstallTypes) { - return; + const installer = new Installer(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, ["@types/commander"], [commander], cb); } - if (response.kind === server.EventEndInstallTypes) { - assert.deepEqual(response.packagesToInstall, [typingsName("commander")]); - seenTelemetryEvent = true; - return; - } - super.sendResponse(response); - } - }; + }); + const sendResponseSpy = spy(installer, "sendResponse") + .setup(_ => _(Arg.is(response => response.kind === server.EventBeginInstallTypes))) + .setup(_ => _(Arg.is(response => response.kind === server.EventEndInstallTypes))) + .setup(_ => _(Arg.any()), { fallback: true }); const projectService = createProjectService(host, { typingsInstaller: installer }); projectService.openClientFile(f1.path); installer.installAll(/*expectedCount*/ 1); - assert.isTrue(seenTelemetryEvent); + installWorkerSpy.revoke(); + sendResponseSpy + .verify(_ => _(Arg.is(response => response.kind === server.EventEndInstallTypes && + response.packagesToInstall.length === 1 && + response.packagesToInstall[0] === typingsName("commander")))) + .revoke(); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]); @@ -1029,7 +999,7 @@ namespace ts.projectSystem { describe("progress notifications", () => { it("should be sent for success", () => { - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const f1 = host.vfs.addFile("/a/app.js", ``); host.vfs.addFile("/a/package.json", `{ "dependencies": { "commander": "1.0.0" } }`); @@ -1041,35 +1011,29 @@ namespace ts.projectSystem { let beginEvent: server.BeginInstallTypes; let endEvent: server.EndInstallTypes; - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - const installedTypings = ["@types/commander"]; - const typingFiles = [commander]; - executeCommand(this, host, installedTypings, typingFiles, cb); - } - sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) { - if (response.kind === server.EventBeginInstallTypes) { - beginEvent = response; - return; + const installer = new Installer(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, ["@types/commander"], [commander], cb); } - if (response.kind === server.EventEndInstallTypes) { - endEvent = response; - return; - } - super.sendResponse(response); - } - }; + }); + const sendResponseSpy = spy(installer, "sendResponse") + .setup(_ => _(Arg.is(response => response.kind === server.EventBeginInstallTypes)), { callback: response => { beginEvent = response; } }) + .setup(_ => _(Arg.is(response => response.kind === server.EventEndInstallTypes)), { callback: response => { endEvent = response; } }) + .setup(_ => _(Arg.any()), { fallback: true }); const projectService = createProjectService(host, { typingsInstaller: installer }); projectService.openClientFile(f1.path); installer.installAll(/*expectedCount*/ 1); - assert.isTrue(!!beginEvent); - assert.isTrue(!!endEvent); + installWorkerSpy.revoke(); + sendResponseSpy + .verify(_ => _(Arg.is(response => response.kind === server.EventBeginInstallTypes))) + .verify(_ => _(Arg.is(response => response.kind === server.EventEndInstallTypes))) + .revoke(); + assert.isTrue(beginEvent.eventId === endEvent.eventId); assert.isTrue(endEvent.installSuccess); host.checkTimeoutQueueLengthAndRun(2); @@ -1079,40 +1043,37 @@ namespace ts.projectSystem { it("should be sent for error", () => { // const host = createServerHost([f1, packageFile]); - const host = new mocks.MockServerHost({ safeList: true }); + const host = new fakes.FakeServerHost({ safeList: true }); const f1 = host.vfs.addFile("/a/app.js", ``); host.vfs.addFile("/a/package.json", `{ "dependencies": { "commander": "1.0.0" } }`); const cachePath = "/a/cache/"; + let beginEvent: server.BeginInstallTypes; let endEvent: server.EndInstallTypes; - const installer = new class extends Installer { - constructor() { - super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); - } - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { - executeCommand(this, host, "", [], cb); - } - sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.BeginInstallTypes | server.EndInstallTypes) { - if (response.kind === server.EventBeginInstallTypes) { - beginEvent = response; - return; + const installer = new Installer(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); + const installWorkerSpy = spy(installer, "installWorker") + .setup(_ => _(Arg.any(), Arg.any(), Arg.any(), Arg.any()), { + callback: (_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) => { + executeCommand(installer, host, "", [], cb); } - if (response.kind === server.EventEndInstallTypes) { - endEvent = response; - return; - } - super.sendResponse(response); - } - }; + }); + const sendResponseSpy = spy(installer, "sendResponse") + .setup(_ => _(Arg.is(response => response.kind === server.EventBeginInstallTypes)), { callback: response => { beginEvent = response; } }) + .setup(_ => _(Arg.is(response => response.kind === server.EventEndInstallTypes)), { callback: response => { endEvent = response; } }) + .setup(_ => _(Arg.any()), { fallback: true }); const projectService = createProjectService(host, { typingsInstaller: installer }); projectService.openClientFile(f1.path); installer.installAll(/*expectedCount*/ 1); - assert.isTrue(!!beginEvent); - assert.isTrue(!!endEvent); + installWorkerSpy.revoke(); + sendResponseSpy + .verify(_ => _(Arg.is(response => response.kind === server.EventBeginInstallTypes))) + .verify(_ => _(Arg.is(response => response.kind === server.EventEndInstallTypes))) + .revoke(); + assert.isTrue(beginEvent.eventId === endEvent.eventId); assert.isFalse(endEvent.installSuccess); checkNumberOfProjects(projectService, { inferredProjects: 1 }); diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts index aa2cc0e6e10..07a187f1030 100644 --- a/src/harness/vfs.ts +++ b/src/harness/vfs.ts @@ -209,7 +209,7 @@ namespace vfs { } private get watchedDirectoriesPrivate() { - return this._watchedDirectories || (this._watchedDirectories = new core.KeyedCollection(this.pathComparer)) + return this._watchedDirectories || (this._watchedDirectories = new core.KeyedCollection(this.pathComparer)); } private get watchedRecursiveDirectoriesSetPrivate() { diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index b4b8a8141f0..aac9b750337 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -1,5 +1,5 @@ /// -/// +/// // TODO(rbuckton): Migrate this to use vfs. @@ -41,34 +41,6 @@ interface Array {}` useWindowsStylePaths?: boolean; } - export function createWatchedSystem(fileOrFolderList: ReadonlyArray, params: TestServerHostCreationParameters = {}) { - // const host = new mocks.MockServerHost({ - // vfs: { - // currentDirectory: params.currentDirectory, - // useCaseSensitiveFileNames: params.useCaseSensitiveFileNames - // }, - // executingFilePath: params.executingFilePath, - // newLine: params.newLine as "\r\n" | "\n" - // }); - // for (const entry of fileOrFolderList) { - // if (typeof entry.content === "string") { - // host.vfs.addFile(entry.path, entry.content); - // } - // else { - // host.vfs.addDirectory(entry.path); - // } - // } - // if (params.useWindowsStylePaths) throw new Error("Not supported"); - const host = new TestServerHost(/*withSafelist*/ false, - params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false, - params.executingFilePath || getExecutingFilePathFromLibFile(), - params.currentDirectory || "/", - fileOrFolderList, - params.newLine, - params.useWindowsStylePaths); - return host; - } - export function createServerHost(fileOrFolderList: ReadonlyArray, params?: TestServerHostCreationParameters): TestServerHost { if (!params) { params = {}; @@ -174,22 +146,22 @@ interface Array {}` } } - export function checkWatchedFiles(host: TestServerHost | mocks.MockServerHost, expectedFiles: string[]) { - if (host instanceof mocks.MockServerHost) { + export function checkWatchedFiles(host: TestServerHost | fakes.FakeServerHost, expectedFiles: string[]) { + if (host instanceof fakes.FakeServerHost) { return checkSortedSet(host.vfs.watchedFiles, expectedFiles); } checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles); } - export function checkWatchedDirectories(host: TestServerHost | mocks.MockServerHost, expectedDirectories: string[], recursive = false) { - if (host instanceof mocks.MockServerHost) { + export function checkWatchedDirectories(host: TestServerHost | fakes.FakeServerHost, expectedDirectories: string[], recursive = false) { + if (host instanceof fakes.FakeServerHost) { return checkSortedSet(recursive ? host.vfs.watchedRecursiveDirectories : host.vfs.watchedNonRecursiveDirectories, expectedDirectories); } checkMapKeys(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories); } - export function checkOutputContains(host: TestServerHost | mocks.MockServerHost, expected: ReadonlyArray) { + export function checkOutputContains(host: TestServerHost | fakes.FakeServerHost, expected: ReadonlyArray) { const mapExpected = arrayToSet(expected); const mapSeen = createMap(); for (const f of host.getOutput()) { @@ -202,7 +174,7 @@ interface Array {}` assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(flatMapIter(mapExpected.keys(), key => key))} in ${JSON.stringify(host.getOutput())}`); } - export function checkOutputDoesNotContain(host: TestServerHost | mocks.MockServerHost, expectedToBeAbsent: string[] | ReadonlyArray) { + export function checkOutputDoesNotContain(host: TestServerHost | fakes.FakeServerHost, expectedToBeAbsent: string[] | ReadonlyArray) { const mapExpectedToBeAbsent = arrayToSet(expectedToBeAbsent); for (const f of host.getOutput()) { assert.isFalse(mapExpectedToBeAbsent.has(f), `Contains ${f} in ${JSON.stringify(host.getOutput())}`); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 726621a540d..0d98d798c0e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -537,7 +537,7 @@ namespace ts.server { } } - updateTypingsForProject(response: SetTypings | InvalidateCachedTypings): void { + updateTypingsForProject(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes): void { const project = this.findProject(response.projectName); if (!project) { return;