Revert changes to unit tests

This commit is contained in:
Ron Buckton 2018-02-05 22:27:55 -08:00
parent 0b963489a7
commit f153b04f35
37 changed files with 10089 additions and 13170 deletions

View File

@ -625,21 +625,19 @@ function cleanPrivatePackage(packageName: string) {
return del([`scripts/${packageName}/dist`]);
}
gulp.task("typemock", () => compilePrivatePackage("typemock"));
gulp.task("vfs-core", () => compilePrivatePackage("vfs-core"));
gulp.task("vfs-errors", () => compilePrivatePackage("vfs-errors"));
gulp.task("vfs-path", ["vfs-core", "vfs-errors"], () => compilePrivatePackage("vfs-path"));
gulp.task("vfs", ["vfs-core", "vfs-errors", "vfs-path", "typemock"], () => compilePrivatePackage("vfs"));
gulp.task("vfs", ["vfs-core", "vfs-errors", "vfs-path"], () => compilePrivatePackage("vfs"));
gulp.task("harness-core", ["vfs-core"], () => compilePrivatePackage("harness-core"));
gulp.task("private-packages", ["typemock", "vfs", "harness-core"]);
gulp.task("private-packages", ["vfs", "harness-core"]);
gulp.task("clean:typemock", () => cleanPrivatePackage("typemock"));
gulp.task("clean:vfs-core", () => cleanPrivatePackage("vfs-core"));
gulp.task("clean:vfs-errors", () => cleanPrivatePackage("vfs-errors"));
gulp.task("clean:vfs-path", ["clean:vfs-core", "clean:vfs-errors"], () => cleanPrivatePackage("vfs-path"));
gulp.task("clean:vfs", ["clean:vfs-core", "clean:vfs-errors", "clean:vfs-path", "clean:typemock"], () => cleanPrivatePackage("vfs"));
gulp.task("clean:vfs", ["clean:vfs-core", "clean:vfs-errors", "clean:vfs-path"], () => cleanPrivatePackage("vfs"));
gulp.task("clean:harness-core", ["clean:vfs-core"], () => cleanPrivatePackage("harness-core"));
gulp.task("clean:private-packages", ["clean:typemock", "clean:vfs", "clean:harness-core"]);
gulp.task("clean:private-packages", ["clean:vfs", "clean:harness-core"]);
// Task to build the tests infrastructure using the built compiler
const run = path.join(builtLocalDirectory, "run.js");
@ -1161,5 +1159,5 @@ gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are:
gulp.task("default", "Runs 'local'", ["local"]);
gulp.task("watch", "Watches the src/ directory for changes and executes runtests-parallel.", [], () => {
gulp.watch(["src/**/*.*", "scripts/typemock/src/**/*.*"], ["runtests-parallel"]);
gulp.watch(["src/**/*.*"], ["runtests-parallel"]);
});

View File

@ -813,7 +813,7 @@ function privatePackage(packageName, prereqs) {
complete();
}
measure(startCompileTime);
});
});
}, { async: true });
}
@ -821,21 +821,19 @@ function cleanPrivatePackage(packageName) {
jake.rmRf(`scripts/${packageName}/dist`);
}
privatePackage("typemock");
privatePackage("vfs-core");
privatePackage("vfs-errors");
privatePackage("vfs-path", ["vfs-core", "vfs-errors"]);
privatePackage("vfs", ["vfs-path", "typemock"]);
privatePackage("vfs", ["vfs-path"]);
privatePackage("harness-core", ["vfs-core"]);
task("private-packages", ["typemock", "vfs", "harness-core"]);
task("private-packages", ["vfs", "harness-core"]);
task("clean-typemock", () => cleanPrivatePackage("typemock"));
task("clean-vfs-core", () => cleanPrivatePackage("vfs-core"));
task("clean-vfs-errors", () => cleanPrivatePackage("vfs-errors"));
task("clean-vfs-path", ["clean-vfs-core", "clean-vfs-errors"], () => cleanPrivatePackage("vfs-path"));
task("clean-vfs", ["clean-vfs-path", "clean-typemock"], () => cleanPrivatePackage("vfs"));
task("clean-vfs", ["clean-vfs-path"], () => cleanPrivatePackage("vfs"));
task("clean-harness-core", ["clean-vfs-core"], () => cleanPrivatePackage("harness-core"));
task("clean-private-packages", ["clean-typemock", "clean-vfs", "clean-harness-core"]);
task("clean-private-packages", ["clean-vfs", "clean-harness-core"]);
// Task to build the tests infrastructure using the built compiler
var run = path.join(builtLocalDirectory, "run.js");
@ -1337,7 +1335,7 @@ desc("Runs tslint on the compiler sources. Optional arguments are: f[iles]=regex
task("lint", ["build-rules"], () => {
if (fold.isTravis()) console.log(fold.start("lint"));
const fileMatcher = process.env.f || process.env.file || process.env.files;
const files = fileMatcher
? `src/**/${fileMatcher}`
: `Gulpfile.ts scripts/generateLocalizedDiagnosticMessages.ts "scripts/tslint/**/*.ts" "src/**/*.ts" --exclude "src/lib/*.d.ts"`;

View File

@ -85,7 +85,6 @@
"travis-fold": "latest",
"ts-node": "latest",
"tslint": "latest",
"typemock": "file:scripts/typemock",
"typescript": "next",
"vinyl": "latest",
"xml2js": "^0.4.19"

View File

@ -1,28 +0,0 @@
const gulp = require("gulp");
const gutil = require("gulp-util");
const sourcemaps = require("gulp-sourcemaps");
const tsb = require("gulp-tsb");
const mocha = require("gulp-mocha");
const del = require("del");
const src = {
compile: tsb.create("tsconfig.json"),
src: () => gulp.src(["src/**/*.ts"]),
dest: () => gulp.dest("dist")
};
gulp.task("clean", () => del(["dist/**/*"]));
gulp.task("build", () => src.src()
.pipe(sourcemaps.init())
.pipe(src.compile())
.pipe(sourcemaps.write(".", { includeContent: false, destPath: "dist" }))
.pipe(gulp.dest("dist")));
gulp.task("test", ["build"], () => gulp
.src(["dist/tests/index.js"], { read: false })
.pipe(mocha({ reporter: "dot" })));
gulp.task("watch", () => gulp.watch(["src/**/*", "tsconfig.json"], ["test"]));
gulp.task("default", ["test"]);

View File

@ -1,35 +0,0 @@
{
"private": true,
"name": "typemock",
"version": "0.0.0",
"description": "JavaScript Mock object framework",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "gulp test"
},
"keywords": [
"javascript",
"mock",
"type",
"typescript"
],
"author": "Ron Buckton (ron.buckton@microsoft.com)",
"license": "Apache-2.0",
"devDependencies": {
"@types/chai": "^4.0.4",
"@types/mocha": "^2.2.27",
"@types/node": "^8.0.20",
"@types/source-map-support": "^0.4.0",
"chai": "^4.1.2",
"del": "^2.0.2",
"gulp": "^3.9.1",
"gulp-mocha": "^4.3.1",
"gulp-sourcemaps": "^2.6.1",
"gulp-tsb": "^2.0.5",
"merge2": "^0.3.6",
"mocha": "^2.2.5",
"source-map-support": "^0.5.0",
"typescript": "^2.6.1"
}
}

View File

@ -1,427 +0,0 @@
/**
* Represents an argument condition used during verification.
*/
export class Arg {
private _validate: (arg: any) => boolean;
private _message: string | Message | List;
private _rest: boolean;
private constructor(condition: (arg: any) => boolean, message: string | Message | List, rest = false) {
this._validate = condition;
this._message = message;
this._rest = rest;
}
/**
* Allows any value.
*/
public static any<T = any>() {
return <T & Arg>new Arg(_ => true, `any`);
}
/**
* Allows a value that matches the specified condition.
* @param constraint The condition used to match the value.
*/
public static is<T = any>(constraint: (arg: T) => boolean) {
return <T & Arg>new Arg(constraint, `is`);
}
/**
* Allows only a null value.
*/
public static null<T = null>() {
return <T & Arg>new Arg(arg => arg === null, `null`);
}
/**
* Allows only a non-null value.
*/
public static notNull<T = any>() {
return <T & Arg>Arg.not(Arg.null());
}
/**
* Allows only an undefined value.
*/
public static undefined<T = undefined>() {
return <T & Arg>new Arg(arg => arg === undefined, `undefined`);
}
/**
* Allows only a non-undefined value.
*/
public static notUndefined<T = any>() {
return <T & Arg>Arg.not(Arg.undefined());
}
/**
* Allows only an undefined or null value.
*/
public static nullOrUndefined<T = null | undefined>() {
return <T & Arg>Arg.or(Arg.null(), Arg.undefined());
}
/**
* Allows only a non-undefined, non-null value.
*/
public static notNullOrUndefined<T = any>() {
return <T & Arg>Arg.not(Arg.nullOrUndefined());
}
/**
* Allows a value that matches either the specified condition, or `undefined`.
* @param condition The condition to match.
*/
public static optional<T = any>(condition: T | T & Arg) {
return <T & Arg>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<T = any>(min: T, max: T) {
return <T & Arg>new Arg(arg => min <= arg && arg <= max, message`between ${min} and ${max}`);
}
/**
* Allows any value in the provided array.
*/
public static in<T = any>(values: object & Iterable<T>) {
return <T & Arg>new Arg(arg => includes(values, arg), message`in ${list(values, ", ")}`);
}
/**
* Allows any value not in the provided array.
*/
public static notIn<T = any>(values: T[]) {
return <T & Arg>Arg.not(Arg.in(values));
}
/**
* Allows any value that matches the provided pattern.
*/
public static match<T = any>(pattern: RegExp) {
return <T & Arg>new Arg(arg => pattern.test(arg), message`matches ${pattern}`);
}
/**
* Allows any string that starts with the specified substring.
*/
public static startsWith(text: string) {
return <string & Arg>new Arg(arg => typeof arg === "string" && arg.startsWith(text), message`starts with ${text}`);
}
/**
* Allows any string that ends with the specified substring.
*/
public static endsWith(text: string) {
return <string & Arg>new Arg(arg => typeof arg === "string" && arg.endsWith(text), message`ends with ${text}`);
}
/**
* Allows any string that includes the specified substring.
*/
public static includes(text: string) {
return <string & Arg>new Arg(arg => typeof arg === "string" && arg.includes(text), message`includes ${text}`);
}
/**
* Allows an array that matches the specified values (or `Arg` conditions), in the same order.
*/
public static array<T>(values: (T | T & Arg)[]): T[] & Arg {
return <any>new Arg(arg => Array.isArray(arg) && Arg.validateAll(values.map(Arg.from), arg), message`array [${list(values, ", ")}]`);
}
/**
* Allows any value with the provided `typeof` tag.
*/
public static typeof(tag: "string"): string & Arg;
/**
* Allows any value with the provided `typeof` tag.
*/
public static typeof(tag: "number"): number & Arg;
/**
* Allows any value with the provided `typeof` tag.
*/
public static typeof(tag: "boolean"): boolean & Arg;
/**
* Allows any value with the provided `typeof` tag.
*/
public static typeof(tag: "symbol"): symbol & Arg;
/**
* Allows any value with the provided `typeof` tag.
*/
public static typeof(tag: "object"): object & null & Arg;
/**
* Allows any value with the provided `typeof` tag.
*/
public static typeof(tag: "function"): ((...args: any[]) => any) & Arg;
/**
* Allows any value with the provided `typeof` tag.
*/
public static typeof(tag: "undefined"): undefined & Arg;
/**
* Allows any value with the provided `typeof` tag.
*/
public static typeof<T = any>(tag: string): T & Arg;
public static typeof<T = any>(tag: string) {
return <T & Arg>new Arg(arg => typeof arg === tag, message`typeof ${tag}`);
}
/**
* Allows any `string`.
*/
public static string() { return this.typeof("string"); }
/**
* Allows any `number`.
*/
public static number() { return this.typeof("number"); }
/**
* Allows any `boolean`.
*/
public static boolean() { return this.typeof("boolean"); }
/**
* Allows any `symbol`.
*/
public static symbol() { return this.typeof("symbol"); }
/**
* Allows any `object` (including functions but excluding `null`).
*/
public static object<T extends object = object>() {
return <T & Arg>new Arg(arg => typeof arg === "object" && arg !== null || typeof arg === "function", `object`);
}
/**
* Allows any `Function` value.
*/
public static function() { return this.typeof("function"); }
/**
* Allows any value that is an instance of the provided function.
* @param type The expected constructor.
*/
public static instanceof<TClass extends { new (...args: any[]): object; prototype: object; }>(type: TClass) {
return <TClass["prototype"] & Arg>new Arg(arg => arg instanceof type, message`instanceof ${type.name}`);
}
/**
* Allows any value that has the provided property names in its prototype chain.
*/
public static has<T>(...names: string[]) {
return <T & Arg>new Arg(arg => count(names, name => has(arg, name)) === names.length, message`has ${list(names, ", ")}`);
}
/**
* Allows any value that has the provided property names on itself but not its prototype chain.
*/
public static hasOwn<T>(...names: string[]) {
return <T & Arg>new Arg(arg => count(names, name => hasOwn(arg, name)) === names.length, message`hasOwn ${list(names, ", ")}`);
}
/**
* Allows any array that contains the specified value.
*/
public static hasElement<T>(value: T | T & Arg) {
return <T[] & Arg>new Arg(arg => includes(arg, value), message`has element ${value}`);
}
/**
* Allows any value that matches the provided condition for the rest of the arguments in the call.
* @param condition The optional condition for each other element.
*/
public static rest<T>(condition?: T | (T & Arg)) {
return arguments.length === 0
? <T & Arg>new Arg(() => true, `rest`, /*rest*/ true)
: <T & Arg>new Arg(arg => Arg.from(condition)._validate(arg), message`rest ${condition}`, /*rest*/ true);
}
/**
* Negates a condition.
*/
public static not<T = any>(condition: T | (T & Arg)) {
return <T & Arg>new Arg(arg => !Arg.from(condition)._validate(arg), message`not ${condition}`);
}
/**
* Combines conditions, where all conditions must be `true`.
*/
public static and<T = any>(...conditions: ((T & Arg) | T)[]) {
return <T & Arg>new Arg(arg => conditions.every(condition => Arg.from(condition)._validate(arg)), list(conditions, " and "));
}
/**
* Combines conditions, where any conditions may be `true`.
*/
public static or<T = any>(...conditions: ((T & Arg) | T)[]) {
return <T & Arg>new Arg(arg => conditions.some(condition => Arg.from(condition)._validate(arg)), list(conditions, " or "));
}
/**
* Ensures the value is a `Condition`
* @param value The value to coerce
* @returns The condition
*/
public static from<T>(value: T) {
return value instanceof Arg ? value :
value === undefined ? Arg.undefined() :
value === null ? Arg.null() :
new Arg(arg => is(arg, value), new Message(() => JSON.stringify(value)));
}
/**
* Validates an argument against a condition
* @param condition The condition to validate.
* @param arg The argument to validate against the condition.
*/
public static validate(condition: Arg, arg: any): boolean {
return condition._validate(arg);
}
/**
* Validates the arguments against the condition.
* @param conditions The conditions to validate.
* @param args The arguments for the execution.
*/
public static validateAll(conditions: ReadonlyArray<Arg>, args: ReadonlyArray<any>): boolean {
const length = Math.max(conditions.length, args.length);
let conditionIndex = 0;
let argIndex = 0;
while (argIndex < length) {
const condition = conditionIndex < conditions.length ? conditions[conditionIndex] : undefined;
const arg = argIndex < args.length ? args[argIndex] : undefined;
if (!condition) return false;
if (argIndex >= args.length && condition._rest) return true;
if (!condition._validate(arg)) return false;
if (!condition._rest) conditionIndex++;
argIndex++;
}
return true;
}
/**
* Gets the message associated with the provided `Arg`.
*/
public static messageFor(arg: Arg) {
return typeof arg._message === "string" ? arg._message : arg._message.toString();
}
/**
* Gets a string that represents this condition.
*/
public toString(): string {
return `<${this._message}>`;
}
}
/**
* SameValueZero (from ECMAScript spec), which has stricter equality sematics than "==" or "===".
*/
function is(x: any, y: any) {
return (x === y) ? (x !== 0 || 1 / x === 1 / y) : (x !== x && y !== y);
}
function message(strings: TemplateStringsArray, ...args: any[]) {
return args.some(isDeferred)
? new Message(() => String.raw(strings, ...args.map(formatArg)))
: String.raw(strings, ...args.map(formatArg));
}
class Message {
private _callback: () => string;
private _message: string | undefined;
constructor(callback: () => string) {
this._callback = callback;
}
public toString(): string {
if (this._message === undefined) this._message = this._callback();
return this._message;
}
}
function list(list: Iterable<any>, separator: string) {
return some(list, isDeferred)
? new List(list, separator)
: formatIterableObject(list, separator);
}
class List {
private _items: Iterable<any>;
private _separator: string;
private _message: string | undefined;
constructor(items: Iterable<any>, separator: string) {
this._items = items;
this._separator = separator;
}
public toString() {
if (this._message === undefined) this._message = formatIterableObject(this._items, this._separator);
return this._message;
}
}
function isDeferred(arg: any): boolean {
return arg instanceof Message || arg instanceof List || arg instanceof Arg;
}
function isIterableObject(value: any): value is Iterable<any> {
return typeof value === "object" && value !== null && typeof value[Symbol.iterator] === "function";
}
function formatArg(arg: any) {
return isIterableObject(arg) ? formatIterableObject(arg, ", ") :
arg instanceof Arg ? Arg.messageFor(arg) :
arg;
}
function formatIterableObject(arg: Iterable<any>, separator: string) {
let result = "";
for (const item of arg) {
if (result) result += separator;
result += item instanceof Arg ? Arg.messageFor(item) : item;
}
return result;
}
function count<T>(array: T[], predicate: (value: T) => boolean): number {
let result = 0;
for (const item of array) {
if (predicate(item)) result++;
}
return result;
}
function has(object: any, key: PropertyKey) {
return Reflect.has(object, key);
}
function hasOwn(object: any, key: PropertyKey) {
return Object.prototype.hasOwnProperty.call(object, key);
}
function includes<T>(object: Iterable<T>, condition: T) {
if (condition instanceof Arg) {
for (const item of object) if (Arg.validate(condition, item)) return true;
return false;
}
if (Array.isArray(object)) return object.indexOf(condition) >= 0;
if (object instanceof Set) return object.has(condition);
for (const item of object) if (is(item, condition)) return true;
return false;
}
function some<T>(object: Iterable<T>, predicate: (a: T) => boolean) {
if (Array.isArray(object)) return object.some(predicate);
for (const item of object) if (predicate(item)) return true;
return false;
}

View File

@ -1,25 +0,0 @@
export { Arg } from "./arg";
export { Times } from "./times";
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<T extends Callable | Constructable = Callable & Constructable>(): Mock<T>;
/**
* Creates a spy on an object or function.
*/
export function spy<T extends object>(target: T): Mock<T>;
/**
* 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<T extends { [P in K]: (...args: any[]) => any }, K extends keyof T>(object: T, propertyKey: K): Spy<T, K>;
export function spy<T extends { [P in K]: (...args: any[]) => any }, K extends keyof T>(object?: T, propertyKey?: K) {
return object === undefined ? Mock.spy() : propertyKey === undefined ? Mock.spy(object) : Mock.spy(object, propertyKey);
}

View File

@ -1,106 +0,0 @@
/**
* Temporarily injects a value into an object property
*/
export class Inject<T extends object, K extends keyof T> {
private _target: T;
private _key: K;
private _injectedValue: any;
private _originalValue: any;
private _installed: boolean = false;
/**
* Temporarily injects a value into an object property
* @param target The target object into which to inject a property
* @param propertyKey The name of the property to inject
* @param injectedValue The value to inject
*/
constructor(target: T, propertyKey: K, injectedValue?: T[K]) {
this._target = target;
this._key = propertyKey;
this._injectedValue = arguments.length === 2 ? target[propertyKey] : injectedValue;
}
public get target() {
return this._target;
}
public get key() {
return this._key;
}
public get injectedValue(): T[K] {
return this._injectedValue;
}
public set injectedValue(value: T[K]) {
if (this._installed) {
this._target[this._key] = value;
}
this._injectedValue = value;
}
public get originalValue(): T[K] {
if (this._installed) {
return this._originalValue;
}
else {
return this.currentValue;
}
}
public get currentValue(): T[K] {
return this._target[this._key];
}
/**
* Gets a value indicating whether `injectedValue` is currently installed.
*/
public get installed(): boolean {
return this._installed;
}
/**
* Installs `injectedValue`
*/
public install(): void {
if (this._installed) return;
this._originalValue = this._target[this._key];
this._target[this._key] = this._injectedValue;
this._installed = true;
}
/**
* Uninstalls `injectedValue`
*/
public uninstall(): void {
if (!this._installed) return;
this._target[this._key] = this._originalValue;
this._installed = false;
this._originalValue = null;
}
/**
* Executes `action` with `injectedValue` installed on `target`.
*/
public static exec<T extends object, K extends keyof T, V>(target: T, propertyKey: K, injectedValue: T[K], action: () => V) {
const injector = new Inject<T, K>(target, propertyKey, injectedValue);
return injector.exec(action);
}
/**
* Executes `action` with `injectedValue` installed.
*/
public exec<V>(action: () => V): V {
if (this._installed) {
return action();
}
try {
this.install();
return action();
}
finally {
this.uninstall();
}
}
}

View File

@ -1,580 +0,0 @@
import { Times } from "./times";
import { Arg } from "./arg";
import { Inject } from "./inject";
const weakHandler = new WeakMap<object, MockHandler<object>>();
const weakMock = new WeakMap<object, Mock<object>>();
export type Callable = (...args: any[]) => any;
export type Constructable = new (...args: any[]) => any;
export interface ThisArg {
this: any;
}
export interface Returns<U> {
return: U;
}
export interface Fallback {
fallback: true;
}
export interface Throws {
throw: any;
}
export interface Callback {
callback: Callable;
}
export type Setup<U> =
| Returns<U> & (ThisArg & Callback | ThisArg | Callback)
| Returns<U>
| 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<T extends object> {
private _handler: MockHandler<T>;
private _proxy: T;
private _revoke: () => void;
/**
* A mock version of another object
* @param target The object to mock.
* @param setups Optional setups to use
*/
constructor(target: T = <T>{}, setups?: Partial<T>) {
this._handler = typeof target === "function"
? new MockFunctionHandler<T & (Callable | Constructable)>()
: new MockHandler<T>();
const { proxy, revoke } = Proxy.revocable<T>(target, this._handler);
this._proxy = proxy;
this._revoke = revoke;
weakHandler.set(proxy, this._handler);
weakMock.set(proxy, this);
if (setups) {
this.setup(setups);
}
}
/**
* Gets the mock version of the target
*/
public get proxy(): T {
return this._proxy;
}
/**
* Creates an empty Mock object.
*/
public static object<T extends object = any>() {
return new Mock(<T>{});
}
/**
* Creates an empty Mock function.
*/
public static function<T extends Callable | Constructable = Callable & Constructable>() {
return new Mock(<T>function () {});
}
/**
* Creates a function spy.
*/
public static spy<T extends Callable | Constructable = Callable & Constructable>(): Mock<T>;
/**
* Creates a spy on an object or function.
*/
public static spy<T extends object>(target: T): Mock<T>;
/**
* 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<T extends { [P in K]: (...args: any[]) => any }, K extends keyof T>(object: T, propertyKey: K): Spy<T, K>;
public static spy<T extends { [P in K]: (...args: any[]) => any }, K extends keyof T>(object?: T, propertyKey?: K) {
return object !== undefined && propertyKey !== undefined
? new Spy(object, propertyKey)
: new Mock(object || function () {});
}
/**
* Gets the mock for an object.
* @param target The target.
*/
public static from<T extends object>(target: T) {
return <Mock<T> | 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<U = any>(callback: (value: T) => U, result?: Setup<U>): 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<T>): this;
public setup<U>(setup: Partial<T> | ((value: T) => U), result?: Setup<U>): this {
if (typeof setup === "function") {
this._handler.setupCall(setup, result);
}
else {
this._handler.setupMembers(setup);
}
return this;
}
/**
* Performs verification that a specific action occurred at least once.
* @param callback A callback that simulates the expected action.
* @param message An optional message to use if verification fails.
* @returns This mock instance.
*/
public verify<U>(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<U>(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<U>(callback: (value: T) => U, times?: Times | string, message?: string): this {
if (typeof times === "string") {
message = times;
times = undefined;
}
if (times === undefined) {
times = Times.atLeastOnce();
}
this._handler.verify(callback, times, message);
return this;
}
public revoke() {
weakMock.delete(this._proxy);
weakHandler.delete(this._proxy);
this._handler.revoke();
this._revoke();
}
}
export class Spy<T extends { [P in K]: (...args: any[]) => any }, K extends keyof T> extends Mock<T[K]> {
private _spy: Inject<any, any> | undefined;
constructor(target: T, propertyKey: K) {
super(target[propertyKey]);
this._spy = new Inject(target, propertyKey, this.proxy);
this._spy.install();
}
public get installed() {
return this._spy ? this._spy.installed : false;
}
public install() {
if (!this._spy) throw new Error("Cannot install a revoked spy.");
this._spy.install();
return this;
}
public uninstall() {
if (this._spy) this._spy.uninstall();
return this;
}
public revoke() {
if (this._spy) {
this._spy.uninstall();
this._spy = undefined;
}
super.revoke();
}
}
class Recording {
public static readonly noThisArg = {};
public readonly trap: "apply" | "construct" | "invoke" | "get" | "set";
public readonly name: PropertyKey | undefined;
public readonly thisArg: any;
public readonly argArray: ReadonlyArray<any>;
public readonly newTarget: any;
public readonly result: Partial<Returns<any> & Throws & Fallback> | undefined;
public readonly callback: Callable | undefined;
private _thisCondition: Arg | undefined;
private _newTargetCondition: Arg | undefined;
private _conditions: ReadonlyArray<Arg> | undefined;
constructor(trap: "apply" | "construct" | "invoke" | "get" | "set", name: PropertyKey | undefined, thisArg: any, argArray: ReadonlyArray<any>, newTarget: any, result: Partial<Returns<any> & Throws & Fallback> | undefined, callback: Callable | undefined) {
this.trap = trap;
this.name = name;
this.thisArg = thisArg;
this.argArray = argArray || [];
this.newTarget = newTarget;
this.result = result;
this.callback = callback;
}
public get thisCondition() {
return this._thisCondition || (this._thisCondition = this.thisArg === Recording.noThisArg ? Arg.any() : Arg.from(this.thisArg));
}
public get newTargetCondition() {
return this._newTargetCondition || (this._newTargetCondition = Arg.from(this.newTarget));
}
public get argConditions() {
return this._conditions || (this._conditions = this.argArray.map(Arg.from));
}
public get kind() {
switch (this.trap) {
case "apply": return "function";
case "construct": return "function";
case "invoke": return "method";
case "get": return "property";
case "set": return "property";
}
}
public static select(setups: ReadonlyArray<Recording>, kind: Recording["kind"], name: PropertyKey | undefined) {
return setups.filter(setup => setup.kind === kind && setup.name === name);
}
public static evaluate(setups: ReadonlyArray<Recording> | undefined, trap: string, name: PropertyKey | undefined, thisArg: any, argArray: any[], newTarget: any, fallback: () => any) {
if (setups && setups.length > 0) {
for (const setup of setups) {
if (setup.match(trap, name, thisArg, argArray, newTarget)) {
const callback = setup.callback;
if (callback) {
Reflect.apply(callback, thisArg, argArray);
}
const result = setup.getResult(fallback);
return trap === "set" ? true : result;
}
}
return trap === "set" ? false : undefined;
}
return fallback();
}
public toString(): string {
return `${this.trap} ${this.name || ""}(${this.argConditions.join(", ")})${this.newTarget ? ` [${this.newTarget.name}]` : ``}`;
}
public match(trap: string, name: PropertyKey | undefined, thisArg: any, argArray: any, newTarget: any) {
return this.trap === trap
&& this.name === name
&& this.matchThisArg(thisArg)
&& Arg.validateAll(this.argConditions, argArray)
&& Arg.validate(this.newTargetCondition, newTarget);
}
public matchRecording(recording: Recording) {
return this.match(recording.trap, recording.name, recording.thisArg, recording.argArray, recording.newTarget)
&& this.matchResult(recording.result);
}
private matchThisArg(thisArg: any) {
return thisArg === Recording.noThisArg
|| Arg.validate(this.thisCondition, thisArg);
}
private matchResult(result: Partial<Returns<any> & 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<T extends object> implements ProxyHandler<T> {
protected readonly overrides = Object.create(null);
protected readonly recordings: Recording[] = [];
protected readonly setups: Recording[] = [];
protected readonly methodTargets = new WeakMap<Function, Function>();
protected readonly methodProxies = new Map<PropertyKey, Function>();
protected readonly methodRevocations = new Set<() => void>();
public get(target: T, name: PropertyKey, receiver: any = target): any {
const setups = Recording.select(this.setups, "property", name);
const result: Partial<Returns<any> & Throws> = {};
const recording = new Recording("get", name, target, [], /*newTarget*/ undefined, result, /*callback*/ undefined);
this.recordings.push(recording);
try {
const value = Recording.evaluate(setups, "get", name, receiver, [], /*newTarget*/ undefined,
() => Reflect.get(this.getTarget(target, name), name, receiver));
return typeof value === "function" ? this.getMethod(name, value) : value;
}
catch (e) {
throw result.throw = e;
}
}
public set(target: T, name: PropertyKey, value: any, receiver: any = target): boolean {
if (typeof value === "function" && this.methodTargets.has(value)) {
value = this.methodTargets.get(value);
}
const setups = Recording.select(this.setups, "property", name);
const result: Partial<Returns<any> & Throws> = {};
const recording = new Recording("set", name, target, [value], /*newTarget*/ undefined, result, /*callback*/ undefined);
this.recordings.push(recording);
try {
const success = Recording.evaluate(setups, "set", name, receiver, [value], /*newTarget*/ undefined,
() => Reflect.set(this.getTarget(target, name), name, value, receiver));
result.return = undefined;
return success;
}
catch (e) {
throw result.throw = e;
}
}
public invoke(proxy: T, name: PropertyKey, method: Function, argArray: any[]): any {
const setups = Recording.select(this.setups, "method", name);
const result: Partial<Returns<any> & Throws> = {};
const recording = new Recording("invoke", name, proxy, argArray, /*newTarget*/ undefined, result, /*callback*/ undefined);
this.recordings.push(recording);
try {
return Recording.evaluate(setups, "invoke", name, proxy, argArray, /*newTarget*/ undefined,
() => Reflect.apply(method, proxy, argArray));
}
catch (e) {
throw result.throw = e;
}
}
public setupCall(callback: (value: any) => any, result: Setup<any> | undefined) {
const recording = this.capture(callback, result);
const existing = this.setups.find(setup => setup.name === recording.name);
if (existing) {
if (existing.kind !== recording.kind) {
throw new Error(`Cannot mix method and property setups for the same member name.`);
}
}
else if (recording.name !== undefined) {
if (recording.kind === "method") {
this.defineMethod(recording.name);
}
else if (recording.kind === "property") {
this.defineAccessor(recording.name);
}
}
this.setups.push(recording);
}
public setupMembers(setup: object) {
for (const propertyKey of Reflect.ownKeys(setup)) {
const descriptor = Reflect.getOwnPropertyDescriptor(setup, propertyKey);
if (descriptor) {
if (propertyKey in this.overrides) {
throw new Error(`Property '${propertyKey.toString()}' already exists.`);
}
Reflect.defineProperty(this.overrides, propertyKey, descriptor);
}
}
}
public verify<U>(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) {
if (expectation.matchRecording(recording)) {
count++;
}
}
times.check(count, message || `An error occured when verifying expectation: ${expectation}`);
}
public getTarget(target: T, name: PropertyKey) {
return name in this.overrides ? this.overrides : target;
}
public getMethod(name: PropertyKey, value: Function): Function {
const proxy = this.methodProxies.get(name);
if (proxy && this.methodTargets.get(proxy) === value) {
return proxy;
}
else {
const { proxy, revoke } = Proxy.revocable(value, new MethodHandler(name));
this.methodProxies.set(name, proxy);
this.methodRevocations.add(revoke);
this.methodTargets.set(proxy, value);
return proxy;
}
}
public revoke() {
for (const revoke of this.methodRevocations) {
revoke();
}
}
protected capture<U>(callback: (value: T) => U, result: Setup<any> | undefined) {
return this.captureCore(<T>{}, new CapturingHandler<T, U>(result), callback);
}
protected captureCore<T extends object, U>(target: T, handler: CapturingHandler<T, U>, callback: (value: T) => U): Recording {
const { proxy, revoke } = Proxy.revocable<T>(target, handler);
try {
callback(proxy);
if (!handler.recording) {
throw new Error("Nothing was captured.");
}
return handler.recording;
}
finally {
revoke();
}
}
private defineMethod(name: PropertyKey) {
const setups = this.setups;
this.setupMembers({
[name](...argArray: any[]) {
return Recording.evaluate(setups, "invoke", name, this, argArray, /*newTarget*/ undefined, () => {});
}
});
}
private defineAccessor(name: PropertyKey) {
const setups = this.setups;
this.setupMembers({
get [name]() {
return Recording.evaluate(setups, "get", name, this, [], /*newTarget*/ undefined, () => {});
},
set [name](value: any) {
Recording.evaluate(setups, "set", name, this, [value], /*newTarget*/ undefined, () => {});
}
});
}
}
class MockFunctionHandler<T extends Callable | Constructable> extends MockHandler<T> {
public apply(target: T, thisArg: any, argArray: any[]): any {
const setups = Recording.select(this.setups, "function", /*name*/ undefined);
const result: Partial<Returns<any> & 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<Returns<any> & 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<U>(callback: (value: T) => U, result: Returns<any> & ThisArg | Returns<any> | Throws & ThisArg | Throws | ThisArg | undefined) {
return this.captureCore(<T>function() {}, new CapturingFunctionHandler<T, U>(result), callback);
}
}
class MethodHandler {
public name: PropertyKey;
constructor(name: PropertyKey) {
this.name = name;
}
public apply(target: Function, thisArgument: any, argumentsList: any[]): any {
const handler = weakHandler.get(thisArgument);
return handler
? handler.invoke(thisArgument, this.name, target, argumentsList)
: Reflect.apply(target, thisArgument, argumentsList);
}
}
class CapturingHandler<T extends object, U> implements ProxyHandler<T> {
public recording: Recording | undefined;
protected readonly callback: Callable | undefined;
protected readonly thisArg: any;
protected readonly result: Returns<U> | Throws | Fallback | undefined;
constructor(result: Partial<Returns<U> & Throws & ThisArg & Callback & Fallback> | undefined) {
this.thisArg = hasOwn(result, "this") ? result.this : Recording.noThisArg;
this.callback = hasOwn(result, "callback") ? result.callback : undefined;
this.result = hasOwn(result, "return") ? { return: result.return } :
hasOwn(result, "throw") ? { throw: result.throw } :
hasOwn(result, "fallback") && result.fallback ? { fallback: true } :
undefined;
}
public get(_target: T, name: PropertyKey, _receiver: any): any {
this.recording = new Recording("get", name, this.thisArg, [], /*newTarget*/ undefined, this.result, this.callback);
return (...argArray: any[]) => { this.recording = new Recording("invoke", name, this.thisArg, argArray, /*newTarget*/ undefined, this.result, this.callback); };
}
public set(_target: T, name: PropertyKey, value: any, _receiver: any): boolean {
this.recording = new Recording("set", name, this.thisArg, [value], /*newTarget*/ undefined, this.result, this.callback);
return true;
}
}
class CapturingFunctionHandler<T extends Callable | Constructable, U> extends CapturingHandler<T, U> {
public apply(_target: T, _thisArg: any, argArray: any[]): any {
this.recording = new Recording("apply", /*name*/ undefined, this.thisArg, argArray, /*newTarget*/ undefined, this.result, this.callback);
return undefined;
}
public construct(_target: T, argArray: any[], newTarget?: any): any {
this.recording = new Recording("construct", /*name*/ undefined, /*thisArg*/ undefined, argArray, newTarget, this.result, this.callback);
return undefined;
}
}
function hasOwn<T extends object, K extends keyof T>(object: Partial<T> | undefined, key: K): object is (T | T & never) & { [P in K]: T[P] } {
return object !== undefined
&& Object.prototype.hasOwnProperty.call(object, key);
}

View File

@ -1,633 +0,0 @@
import "./sourceMapSupport";
import { Arg } from "../arg";
import { assert } from "chai";
describe("arg", () => {
describe("any", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.any());
// act
const result = Arg.validate(target, "a");
// assert
assert.isTrue(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.any());
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<any>`);
});
});
describe("is", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.is(value => value === "a"));
// act
const result = Arg.validate(target, "a");
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.is(value => value === "a"));
// act
const result = Arg.validate(target, "b");
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.is(value => value === "a"));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<is>`);
});
});
describe("notNull", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.notNull());
// act
const result = Arg.validate(target, {});
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.notNull());
// act
const result = Arg.validate(target, null);
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.notNull());
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<not null>`);
});
});
describe("null", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.null());
// act
const result = Arg.validate(target, null);
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.null());
// act
const result = Arg.validate(target, {});
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.null());
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<null>`);
});
});
describe("notUndefined", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.notUndefined());
// act
const result = Arg.validate(target, {});
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.notUndefined());
// act
const result = Arg.validate(target, undefined);
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.notUndefined());
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<not undefined>`);
});
});
describe("undefined", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.undefined());
// act
const result = Arg.validate(target, undefined);
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.undefined());
// act
const result = Arg.validate(target, {});
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.undefined());
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<undefined>`);
});
});
describe("notNullOrUndefined", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.notNullOrUndefined());
// act
const result = Arg.validate(target, {});
// assert
assert.isTrue(result);
});
it("invalid (null)", () => {
// arrange
const target = Arg.from(Arg.notNullOrUndefined());
// act
const result = Arg.validate(target, null);
// assert
assert.isFalse(result);
});
it("invalid (undefined)", () => {
// arrange
const target = Arg.from(Arg.notNullOrUndefined());
// act
const result = Arg.validate(target, undefined);
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.notNullOrUndefined());
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<not null or undefined>`);
});
});
describe("nullOrUndefined", () => {
it("valid (null)", () => {
// arrange
const target = Arg.from(Arg.nullOrUndefined());
// act
const result = Arg.validate(target, null);
// assert
assert.isTrue(result);
});
it("valid (undefined)", () => {
// arrange
const target = Arg.from(Arg.nullOrUndefined());
// act
const result = Arg.validate(target, undefined);
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.nullOrUndefined());
// act
const result = Arg.validate(target, {});
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.nullOrUndefined());
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<null or undefined>`);
});
});
describe("between", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.between(1, 3));
// act
const min = Arg.validate(target, 1);
const mid = Arg.validate(target, 2);
const max = Arg.validate(target, 3);
// assert
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);
const after = Arg.validate(target, 4);
// assert
assert.isFalse(before);
assert.isFalse(after);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.between(1, 3));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<between 1 and 3>`);
});
});
describe("in", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.in(["a", "b"]));
// act
const result = Arg.validate(target, "a");
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.in(["a", "b"]));
// act
const result = Arg.validate(target, "c");
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.in(["a", "b"]));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<in a, b>`);
});
});
describe("notIn", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.notIn(["a", "b"]));
// act
const result = Arg.validate(target, "c");
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.notIn(["a", "b"]));
// act
const result = Arg.validate(target, "a");
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.notIn(["a", "b"]));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<not in a, b>`);
});
});
describe("match", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.match(/^a$/));
// act
const result = Arg.validate(target, "a");
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.match(/^a$/));
// act
const result = Arg.validate(target, "b");
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.match(/^a$/));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<matches /^a$/>`);
});
});
describe("typeof", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.typeof("number"));
// act
const result = Arg.validate(target, 1);
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.typeof("number"));
// act
const result = Arg.validate(target, "a");
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.typeof("number"));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<typeof number>`);
});
});
describe("instanceof", () => {
it("valid", () => {
// arrange
class C {}
const target = Arg.from(Arg.instanceof(C));
// act
const result = Arg.validate(target, new C());
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
class C {}
const target = Arg.from(Arg.instanceof(C));
// act
const result = Arg.validate(target, {});
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
class C {}
const target = Arg.from(Arg.instanceof(C));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<instanceof C>`);
});
});
describe("has", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.has("a"));
// act
const own = Arg.validate(target, { a: 1 });
const proto = Arg.validate(target, { __proto__: { a: 1 } });
// assert
assert.isTrue(own);
assert.isTrue(proto);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.has("a"));
// act
const result = Arg.validate(target, { b: 1 });
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.has("a"));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<has a>`);
});
});
describe("hasOwn", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.hasOwn("a"));
// act
const own = Arg.validate(target, { a: 1 });
// assert
assert.isTrue(own);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.hasOwn("a"));
// act
const result = Arg.validate(target, { b: 1 });
const proto = Arg.validate(target, { __proto__: { a: 1 } });
// assert
assert.isFalse(result);
assert.isFalse(proto);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.hasOwn("a"));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<hasOwn a>`);
});
});
describe("rest", () => {
describe("no condition", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.rest());
// act
const empty = Arg.validateAll([target], []);
const multiple = Arg.validateAll([target], ["a", "b"]);
// assert
assert.isTrue(empty);
assert.isTrue(multiple);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.rest());
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<rest>`);
});
});
describe("condition", () => {
it("valid", () => {
// arrange
const target = Arg.from(Arg.rest(Arg.typeof("string")));
// act
const empty = Arg.validateAll([target], []);
const multiple = Arg.validateAll([target], ["a", "b"]);
// assert
assert.isTrue(empty);
assert.isTrue(multiple);
});
it("invalid", () => {
// arrange
const target = Arg.from(Arg.rest(Arg.typeof("string")));
// act
const result = Arg.validateAll([target], ["a", 1]);
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from(Arg.rest(Arg.typeof("string")));
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<rest typeof string>`);
});
});
});
describe("from", () => {
it("valid", () => {
// arrange
const target = Arg.from("a");
// act
const result = Arg.validate(target, "a");
// assert
assert.isTrue(result);
});
it("invalid", () => {
// arrange
const target = Arg.from("a");
// act
const result = Arg.validate(target, "b");
// assert
assert.isFalse(result);
});
it("toString", () => {
// arrange
const target = Arg.from("a");
// act
const result = target.toString();
// assert
assert.strictEqual(result, `<"a">`);
});
});
});

View File

@ -1,5 +0,0 @@
import "./argTests";
import "./timesTests";
import "./mockTests";
import "./injectTests";
import "./timersTests";

View File

@ -1,79 +0,0 @@
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());
});
});

View File

@ -1,362 +0,0 @@
import "./sourceMapSupport";
import { Mock } from "../mock";
import { Arg } from "../arg";
import { Times } from "../times";
import { recordError } from "./utils";
import { assert } from "chai";
describe("mock", () => {
describe("no setup", () => {
it("get (exists)", () => {
// arrange
const target = { a: 1 };
const mock = new Mock(target);
// act
const result = mock.proxy.a;
// assert
assert.equal(result, 1);
});
it("get (missing)", () => {
// arrange
const mock = new Mock<{ a?: number }>();
// act
const result = mock.proxy.a;
// 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);
});
});
describe("setup", () => {
describe("using object", () => {
it("get", () => {
// arrange
const target = { a: 1 };
const mock = new Mock(target, { get a() { return 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(_: 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, <any>{ b: 2 });
// act
const result = (<any>mock.proxy).b;
// assert
assert.equal(2, result);
});
it("new method", () => {
// arrange
const target = { a: 1 };
const mock = new Mock(target, <any>{ b() { return 2; } });
// act
const result = (<any>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>(_ => 0);
mock.setup(_ => _(Arg.number()), { return: 2 });
// act
const result = mock.proxy(1);
// assert
assert.strictEqual(result, 2);
});
});
});
describe("verify", () => {
describe("no setup", () => {
describe("get", () => {
it("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("called passes", () => {
// arrange
const target = { a: 1 };
const mock = new Mock(target);
mock.proxy.a;
// act
const e = recordError(() => mock.verify(_ => _.a, Times.once()));
// assert
assert.isUndefined(e);
});
});
describe("set", () => {
it("not called throws", () => {
// arrange
const target = { a: 1 };
const mock = new Mock(target);
// act
const e = recordError(() => mock.verify(_ => _.a = 2, Times.once()));
// assert
assert.instanceOf(e, Error);
});
it("called passes", () => {
// arrange
const target = { a: 1 };
const mock = new Mock(target);
mock.proxy.a = 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);
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();
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 } });
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;
}
});
mock.proxy.a(3);
// act
const e = recordError(() => mock.verify(_ => _.a(Arg.number()), Times.once()));
// assert
assert.isUndefined(e);
});
});
});

View File

@ -1,3 +0,0 @@
import { install } from "source-map-support";
install();

View File

@ -1,305 +0,0 @@
import "./sourceMapSupport";
import { Mock } from "../mock";
import { Arg } from "../arg";
import { Times } from "../times";
import { Timers } from "../timers";
import { assert } from "chai";
describe("timers", () => {
describe("immediate", () => {
it("set adds entry, does not invoke", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
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.verify(_ => _(), Times.none());
});
it("set/clear", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
const handle = target.setImmediate(spy.proxy);
target.clearImmedate(handle);
const pending = target.getPending();
// assert
assert.strictEqual(pending.length, 0);
spy.verify(_ => _(), Times.none());
});
it("set one and execute", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.setImmediate(spy.proxy);
const count = target.executeImmediates();
// assert
assert.strictEqual(count, 1);
spy.verify(_ => _(), Times.once());
});
it("set one with arg and execute", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.setImmediate(spy.proxy, "a");
const count = target.executeImmediates();
// assert
assert.strictEqual(count, 1);
spy.verify(_ => _(Arg.typeof("string")), Times.once());
});
it("nested with maxDepth = 0", () => {
// arrange
const target = new Timers();
const spy = Mock.spy(() => { target.setImmediate(spy.proxy); });
// act
target.setImmediate(spy.proxy);
const count = target.executeImmediates(/*maxDepth*/ 0);
// assert
assert.strictEqual(count, 1);
spy.verify(_ => _(), Times.once());
});
it("nested with maxDepth = 1", () => {
// arrange
const target = new Timers();
const spy = Mock.spy(() => { target.setImmediate(spy.proxy); });
// act
target.setImmediate(spy.proxy);
const count = target.executeImmediates(/*maxDepth*/ 1);
// assert
assert.strictEqual(count, 2);
spy.verify(_ => _(), Times.exactly(2));
});
});
describe("timeout", () => {
it("set adds entry, does not invoke", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
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.verify(_ => _(), Times.none());
});
it("set/clear", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
const handle = target.setTimeout(spy.proxy, 0);
target.clearTimeout(handle);
const pending = target.getPending();
// assert
assert.strictEqual(pending.length, 0);
spy.verify(_ => _(), Times.none());
});
it("set adds future entry, advance prior to due does not invoke", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.setTimeout(spy.proxy, 10);
const count = target.advance(9);
// assert
assert.strictEqual(count, 0);
spy.verify(_ => _(), Times.none());
});
it("set adds future entry, advance to due invokes", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.setTimeout(spy.proxy, 10);
const count = target.advance(10);
// assert
assert.strictEqual(count, 1);
spy.verify(_ => _(), Times.once());
});
it("5 nested sets throttle", () => {
// arrange
const target = new Timers();
const spy = new Mock(() => { target.setTimeout(spy.proxy, 0); });
// act
target.setTimeout(spy.proxy, 0);
const count = target.advance(1);
// assert
assert.strictEqual(count, 5);
spy.verify(_ => _(), Times.exactly(5));
});
});
describe("interval", () => {
it("set adds entry, does not invoke", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
const handle = target.setInterval(spy.proxy, 0);
const pending = target.getPending({ kind: "interval", ms: 10 });
// assert
assert.strictEqual(pending.length, 1);
assert.strictEqual(pending[0].kind, "interval");
assert.strictEqual(pending[0].interval, 10);
assert.isDefined(handle);
spy.verify(_ => _(), Times.none());
});
it("set/clear", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
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.verify(_ => _(), Times.none());
});
it("set adds future entry, advance prior to due does not invoke", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.setInterval(spy.proxy, 10);
const count = target.advance(9);
// assert
assert.strictEqual(count, 0);
spy.verify(_ => _(), Times.none());
});
it("set adds future entry, advance to due invokes", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.setInterval(spy.proxy, 10);
const count = target.advance(10);
// assert
assert.strictEqual(count, 1);
spy.verify(_ => _(), Times.once());
});
it("set adds future entry, advance to due twice invokes twice", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.setInterval(spy.proxy, 10);
const count = target.advance(20);
// assert
assert.strictEqual(count, 2);
spy.verify(_ => _(), Times.exactly(2));
});
it("set adds future entry, remove before second due time", () => {
// arrange
const target = new Timers();
const spy = new Mock(() => { target.clearInterval(handle); });
// act
const handle = target.setInterval(spy.proxy, 10);
const count = target.advance(20);
// assert
assert.strictEqual(count, 1);
spy.verify(_ => _(), Times.exactly(1));
});
});
describe("frame", () => {
it("request adds entry, does not invoke", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
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.verify(_ => _(), Times.none());
});
it("request/cancel", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
const handle = target.requestAnimationFrame(spy.proxy);
target.cancelAnimationFrame(handle);
const pending = target.getPending();
// assert
assert.strictEqual(pending.length, 0);
spy.verify(_ => _(), Times.none());
});
it("request and advance past one frame", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.requestAnimationFrame(spy.proxy);
const count = target.advance(16);
// assert
assert.strictEqual(count, 1);
spy.verify(_ => _(Arg.number()), Times.once());
});
it("requests clamped to 16ms", () => {
// arrange
const target = new Timers();
const spy = Mock.function();
// act
target.requestAnimationFrame(spy.proxy);
target.advance(10);
target.requestAnimationFrame(spy.proxy);
const count = target.advance(16);
// assert
assert.strictEqual(count, 2);
spy.verify(_ => _(Arg.number()), Times.exactly(2));
});
});
});

View File

@ -1,236 +0,0 @@
import "./sourceMapSupport";
import { Times } from "../times";
import { theory, recordError } from "./utils";
import { assert } from "chai";
describe("times", () => {
function makeTimesNoneValidationData(): any[][]{
return [
[0, true],
[1, false]
];
}
theory("Times.none validation", makeTimesNoneValidationData, function (count: number, expected: boolean): void {
// arrange
const times = Times.none();
// act
const result = times.validate(count);
// assert
assert.equal(expected, result);
});
function makeTimesOnceValidationData(): any[][]{
return [
[0, false],
[1, true],
[2, false]
];
}
theory("Times.once validation", makeTimesOnceValidationData, function (count: number, expected: boolean): void {
// arrange
const times = Times.once();
// act
const result = times.validate(count);
// assert
assert.equal(expected, result);
});
function makeTimesAtLeastOnceValidationData(): any[] {
return [
[0, false],
[1, true],
[2, true]
];
}
theory("Times.atLeastOnce validation", makeTimesAtLeastOnceValidationData, function (count: number, expected: boolean): void {
// arrange
const times = Times.atLeastOnce();
// act
const result = times.validate(count);
// assert
assert.equal(expected, result);
});
function makeTimesAtMostOnceValidationData(): any[][]{
return [
[0, true],
[1, true],
[2, false]
];
}
theory("Times.atMostOnce validation", makeTimesAtMostOnceValidationData, function (count: number, expected: boolean): void {
// arrange
const times = Times.atMostOnce();
// act
const result = times.validate(count);
// assert
assert.equal(expected, result);
});
function makeTimesExactlyValidationData(): any[][]{
return [
[0, 0, true],
[0, 1, false],
[1, 0, false],
[1, 1, true]];
}
theory("Times.exactly validation", makeTimesExactlyValidationData, function (expectedCount: number, count: number, expectedResult: boolean): void {
// arrange
const times = Times.exactly(expectedCount);
// act
const result = times.validate(count);
// assert
assert.equal(expectedResult, result);
});
function makeTimesAtLeastValidationData(): any[][]{
return [
[0, 0, true],
[0, 1, true],
[1, 0, false],
[1, 1, true],
[1, 2, true]
];
}
theory("Times.atLeast validation", makeTimesAtLeastValidationData, function (expectedCount: number, count: number, expectedResult: boolean): void {
// arrange
const times = Times.atLeast(expectedCount);
// act
const result = times.validate(count);
// assert
assert.equal(expectedResult, result);
});
function makeTimesAtMostValidationData(): any[][]{
return [
[0, 0, true],
[0, 1, false],
[1, 0, true],
[1, 1, true],
[1, 2, false]
];
}
theory("Times.atMost validation", makeTimesAtMostValidationData, function (expectedCount: number, count: number, expectedResult: boolean): void {
// arrange
const times = Times.atMost(expectedCount);
// act
const result = times.validate(count);
// assert
assert.equal(expectedResult, result);
});
function makeTimesBetweenValidationData(): any[][]{
return [
[1, 2, 0, false],
[1, 2, 1, true],
[1, 2, 2, true],
[1, 2, 3, false]
];
}
theory("Times.between validation", makeTimesBetweenValidationData, function (min: number, max: number, count: number, expectedResult: boolean): void {
// arrange
const times = Times.between(min, max);
// act
const result = times.validate(count);
// assert
assert.equal(expectedResult, result);
});
function makeTimesToStringData(): any[][]{
return [
[Times.none(), "<never>"],
[Times.once(), "<exactly once>"],
[Times.atLeastOnce(), "<at least once>"],
[Times.atMostOnce(), "<at most once>"],
[Times.atLeast(2), "<at least 2 time(s)>"],
[Times.atMost(2), "<at most 2 time(s)>"],
[Times.exactly(2), "<exactly 2 time(s)>"],
[Times.between(1, 2), "<between 1 and 2 time(s)>"]
];
}
theory("Times.toString", makeTimesToStringData, function (times: Times, expected: string): void {
// arrange
// act
const result = times.toString();
// assert
assert.equal(expected, result);
});
function makeTimesCheckThrowsData(): any[][]{
return [
[Times.none(), 1],
[Times.once(), 0],
[Times.once(), 2],
[Times.atLeastOnce(), 0],
[Times.atMostOnce(), 2],
[Times.atLeast(2), 1],
[Times.atMost(2), 3],
[Times.exactly(1), 0],
[Times.exactly(1), 2],
[Times.between(1, 2), 0],
[Times.between(1, 2), 3]
]
}
theory("Times.check throws", makeTimesCheckThrowsData, (times: Times, count: number) => {
// arrange
// act
const e = recordError(() => times.check(count, "test"));
// assert
assert.instanceOf(e, Error);
});
function makeTimesCheckPassesData(): any[][] {
return [
[Times.none(), 0],
[Times.once(), 1],
[Times.atLeastOnce(), 1],
[Times.atLeastOnce(), 2],
[Times.atMostOnce(), 1],
[Times.atMostOnce(), 0],
[Times.atLeast(2), 2],
[Times.atLeast(2), 3],
[Times.atMost(2), 2],
[Times.atMost(2), 1],
[Times.exactly(1), 1],
[Times.between(1, 2), 1],
[Times.between(1, 2), 2]
];
}
theory("Times.check passes", makeTimesCheckPassesData, (times: Times, count: number) => {
// arrange
// act
const e = recordError(() => times.check(count, "test"));
// assert
assert.isUndefined(e);
});
});

View File

@ -1,17 +0,0 @@
export function theory(name: string, data: any[][] | (() => any[][]), callback: (...args: any[]) => any) {
describe(name, () => {
for (const row of typeof data === "function" ? data() : data) {
it(row.toString(), () => callback(...row));
}
});
}
export function recordError(action: () => void): Error | undefined {
try {
action();
return undefined;
}
catch (e) {
return e;
}
}

View File

@ -1,479 +0,0 @@
export interface Immediate {
readonly kind: "immediate";
readonly handle: number;
readonly callback: (...args: any[]) => void;
readonly args: ReadonlyArray<any>;
}
export interface Timeout {
readonly kind: "timeout";
readonly handle: number;
readonly callback: (...args: any[]) => void;
readonly args: ReadonlyArray<any>;
}
export interface Interval {
readonly kind: "interval";
readonly handle: number;
readonly callback: (...args: any[]) => void;
readonly args: ReadonlyArray<any>;
readonly interval: number;
}
export interface AnimationFrame {
readonly kind: "frame";
readonly handle: number;
readonly callback: (time: number) => void;
}
export type Timer = Immediate | Timeout | Interval | AnimationFrame;
type NonImmediateTimer = Timeout | Interval | AnimationFrame;
interface Due<T extends Timer> {
timer: T;
due: number;
depth?: number;
enabled?: boolean;
timeline?: boolean;
}
const MAX_INT32 = 2 ** 31 - 1;
const MIN_TIMEOUT_VALUE = 4;
const CLAMP_TIMEOUT_NESTING_LEVEL = 5;
/**
* Programmatic control over timers.
*/
export class Timers {
public static readonly MAX_DEPTH = MAX_INT32;
private _nextHandle = 1;
private _immediates = new Map<number, Due<Immediate>>();
private _timeouts = new Map<number, Due<Timeout>>();
private _intervals = new Map<number, Due<Interval>>();
private _frames = new Map<number, Due<AnimationFrame>>();
private _timeline: Due<NonImmediateTimer>[] = [];
private _time: number;
private _depth = 0;
constructor() {
this._time = 0;
// bind each timer method so that it can be detached from this instance.
this.setImmediate = this.setImmediate.bind(this);
this.clearImmedate = this.clearImmedate.bind(this);
this.setTimeout = this.setTimeout.bind(this);
this.clearTimeout = this.clearTimeout.bind(this);
this.setInterval = this.setInterval.bind(this);
this.clearInterval = this.clearInterval.bind(this);
this.requestAnimationFrame = this.requestAnimationFrame.bind(this);
this.cancelAnimationFrame = this.cancelAnimationFrame.bind(this);
}
/**
* Get the current time.
*/
public get time(): number {
return this._time;
}
/**
* Gets the time of the last scheduled timer (not including repeating intervals).
*/
public get endTime(): number {
return this._timeline && this._timeline.length > 0
? this._timeline[this._timeline.length - 1].due
: this._time;
}
/**
* Gets the estimated time remaining.
*/
public get remainingTime(): number {
return this.endTime - this.time;
}
public getPending(options: { kind: "immediate", ms?: number }): Immediate[];
public getPending(options: { kind: "timeout", ms?: number }): Timeout[];
public getPending(options: { kind: "interval", ms?: number }): Interval[];
public getPending(options: { kind: "frame", ms?: number }): AnimationFrame[];
public getPending(options?: { kind?: Timer["kind"], ms?: number }): Timer[];
public getPending(options: { kind?: Timer["kind"], ms?: number } = {}): Timer[] {
const { kind, ms = 0 } = options;
if (ms < 0) throw new TypeError("Argument 'ms' out of range.");
const dueTimers: Due<Timer>[] = [];
if (!kind || kind === "immediate") {
this.copyImmediates(dueTimers);
}
if (kind !== "immediate") {
this.copyTimelineBefore(dueTimers, this._time + ms, kind);
}
return dueTimers.map(dueTimer => dueTimer.timer);
}
/**
* Advance the current time and trigger callbacks, returning the number of callbacks triggered.
* @param ms The number of milliseconds to advance.
* @param maxDepth The maximum depth for nested `setImmediate` calls to continue processing.
* - Use `0` (default) to disable processing of nested `setImmediate` calls.
* - Use `Timers.MAX_DEPTH` to continue processing nested `setImmediate` calls up to the maximum depth.
*/
public advance(ms: number, maxDepth = 0): number {
if (ms < 0) throw new TypeError("Argument 'ms' out of range.");
if (maxDepth < 0) throw new TypeError("Argument 'maxDepth' out of range.");
let count = 0;
const endTime = this._time + (ms | 0);
while (true) {
if (maxDepth >= 0) {
count += this.executeImmediates(maxDepth);
maxDepth--;
}
const dueTimer = this.dequeueIfBefore(endTime);
if (dueTimer) {
this._time = dueTimer.due;
this.executeTimer(dueTimer);
count++;
}
else {
this._time = endTime;
return count;
}
}
}
/**
* Advance the current time to the estimated end time and trigger callbacks, returning the number of callbacks triggered.
* @param maxDepth The maximum depth for nested `setImmediate` calls to continue processing.
* - Use `0` (default) to disable processing of nested `setImmediate` calls.
* - Use `Timers.MAX_DEPTH` to continue processing nested `setImmediate` calls up to the maximum depth.
*/
public advanceToEnd(maxDepth = 0) {
return this.advance(this.remainingTime, maxDepth);
}
/**
* Execute any pending immediate timers, returning the number of timers triggered.
* @param maxDepth The maximum depth for nested `setImmediate` calls to continue processing.
* - Use `0` (default) to disable processing of nested `setImmediate` calls.
* - Use `Timers.MAX_DEPTH` to continue processing nested `setImmediate` calls up to the maximum depth.
*/
public executeImmediates(maxDepth = 0): number {
if ((maxDepth |= 0) < 0) throw new TypeError("Argument 'maxDepth' out of range.");
const dueTimers: Due<Timer>[] = [];
this.copyImmediates(dueTimers);
let count = this.executeTimers(dueTimers);
for (let depth = 0; depth < maxDepth && this._immediates.size > 0; depth++) {
count += this.executeImmediates();
}
return count;
}
public setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
if (this._depth >= Timers.MAX_DEPTH) {
throw new Error("callback nested too deeply.");
}
const timer: Immediate = { kind: "immediate", handle: this._nextHandle++, callback, args };
const dueTimer: Due<Immediate> = { timer, due: -1 };
this.addTimer(this._immediates, dueTimer);
return timer.handle;
}
public clearImmedate(timerId: any): void {
const dueTimer = this._immediates.get(timerId);
if (dueTimer) {
this.deleteTimer(this._immediates, dueTimer);
}
}
public setTimeout(callback: (...args: any[]) => void, timeout: number, ...args: any[]): any {
if (this._depth >= Timers.MAX_DEPTH) {
throw new Error("callback nested too deeply.");
}
if ((timeout |= 0) < 0) timeout = 0;
if (this._depth >= CLAMP_TIMEOUT_NESTING_LEVEL && timeout < MIN_TIMEOUT_VALUE) {
timeout = MIN_TIMEOUT_VALUE;
}
const timer: Timeout = { kind: "timeout", handle: this._nextHandle++, callback, args };
const dueTimer: Due<Timeout> = { timer, due: this._time + timeout };
this.addTimer(this._timeouts, dueTimer);
this.addToTimeline(dueTimer);
return timer.handle;
}
public clearTimeout(timerId: any): void {
const dueTimer = this._timeouts.get(timerId);
if (dueTimer) {
this.deleteTimer(this._timeouts, dueTimer);
this.removeFromTimeline(dueTimer);
}
}
public setInterval(callback: (...args: any[]) => void, interval: number, ...args: any[]): any {
if (this._depth >= Timers.MAX_DEPTH) {
throw new Error("callback nested too deeply.");
}
if ((interval |= 0) < 10) interval = 10;
const timer: Interval = { kind: "interval", handle: this._nextHandle++, callback, args, interval };
const dueTimer: Due<Interval> = { timer, due: this._time + interval };
this.addTimer(this._intervals, dueTimer);
this.addToTimeline(dueTimer);
return timer.handle;
}
public clearInterval(timerId: any): void {
const dueTimer = this._intervals.get(timerId);
if (dueTimer) {
this.deleteTimer(this._intervals, dueTimer);
this.removeFromTimeline(dueTimer);
}
}
public requestAnimationFrame(callback: (time: number) => void): any {
if (this._depth >= Timers.MAX_DEPTH) {
throw new Error("callback nested too deeply.");
}
const timer: AnimationFrame = { kind: "frame", handle: this._nextHandle++, callback };
const dueTimer: Due<AnimationFrame> = { timer, due: this.nextFrameDueTime() };
this.addTimer(this._frames, dueTimer);
this.addToTimeline(dueTimer);
return timer.handle;
}
public cancelAnimationFrame(timerId: any): void {
const dueTimer = this._frames.get(timerId);
if (dueTimer) {
this.deleteTimer(this._frames, dueTimer);
this.removeFromTimeline(dueTimer);
}
}
private nextFrameDueTime() {
return this._time + this.nextFrameDelta();
}
private nextFrameDelta() {
return 16 - this._time % 16;
}
private addTimer<T extends Timer>(timers: Map<number, Due<T>>, dueTimer: Due<T>) {
if (dueTimer.enabled) return;
timers.set(dueTimer.timer.handle, dueTimer);
dueTimer.depth = this._depth + 1;
dueTimer.enabled = true;
}
private deleteTimer<T extends Timer>(timers: Map<number, Due<T>>, dueTimer: Due<T>) {
if (!dueTimer.enabled) return;
timers.delete(dueTimer.timer.handle);
dueTimer.enabled = false;
}
private executeTimers(dueTimers: Due<Timer>[]) {
let count = 0;
for (const dueTimer of dueTimers) {
this.executeTimer(dueTimer);
count++;
}
return count;
}
private executeTimer(dueTimer: Due<Timer>) {
switch (dueTimer.timer.kind) {
case "immediate": return this.executeImmediate(<Due<Immediate>>dueTimer);
case "timeout": return this.executeTimeout(<Due<Timeout>>dueTimer);
case "interval": return this.executeInterval(<Due<Interval>>dueTimer);
case "frame": return this.executeAnimationFrame(<Due<AnimationFrame>>dueTimer);
}
}
private executeImmediate(dueTimer: Due<Immediate>) {
if (!dueTimer.enabled) return;
this.deleteTimer(this._immediates, dueTimer);
this.executeCallback(dueTimer.depth, dueTimer.timer.callback, ...dueTimer.timer.args);
}
private executeTimeout(dueTimer: Due<Timeout>) {
if (!dueTimer.enabled) return;
this.deleteTimer(this._timeouts, dueTimer);
this.removeFromTimeline(dueTimer);
this.executeCallback(dueTimer.depth, dueTimer.timer.callback, ...dueTimer.timer.args);
}
private executeInterval(dueTimer: Due<Interval>) {
if (!dueTimer.enabled) return;
this.removeFromTimeline(dueTimer);
this.executeCallback(dueTimer.depth, dueTimer.timer.callback, ...dueTimer.timer.args);
if (dueTimer.enabled) {
dueTimer.due += dueTimer.timer.interval;
this.addToTimeline(dueTimer);
}
}
private executeAnimationFrame(dueTimer: Due<AnimationFrame>) {
if (!dueTimer.enabled) return;
this.deleteTimer(this._frames, dueTimer);
this.removeFromTimeline(dueTimer);
this.executeCallback(dueTimer.depth, dueTimer.timer.callback, this._time);
}
private executeCallback(depth = 0, callback: (...args: any[]) => void, ...args: any[]) {
const savedDepth = this._depth;
this._depth = depth;
try {
callback(...args);
}
finally {
this._depth = savedDepth;
}
}
private dequeueIfBefore(dueTime: number) {
if (this._timeline.length > 0) {
const dueTimer = this._timeline[0];
if (dueTimer.due <= dueTime) {
this._timeline.shift();
dueTimer.timeline = false;
return dueTimer;
}
}
}
private copyImmediates(dueTimers: Due<Timer>[]) {
for (const dueTimer of this._immediates.values()) {
dueTimers.push(dueTimer);
}
}
private copyTimelineBefore(dueTimers: Due<Timer>[], dueTime: number, kind?: Timer["kind"]) {
for (const dueTimer of this._timeline) {
if (dueTimer.due <= dueTime && (!kind || dueTimer.timer.kind === kind)) {
dueTimers.push(dueTimer);
}
}
}
private addToTimeline(dueTimer: Due<NonImmediateTimer>) {
if (dueTimer.timeline) return;
let index = binarySearch(this._timeline, dueTimer, getDueTime, compareTimestamps);
if (index < 0) {
index = ~index;
}
else {
while (index < this._timeline.length) {
if (this._timeline[index].due > dueTimer.due) {
break;
}
index++;
}
}
insertAt(this._timeline, index, dueTimer);
dueTimer.timeline = true;
}
private removeFromTimeline(dueTimer: Due<NonImmediateTimer>) {
if (dueTimer.timeline) {
let index = binarySearch(this._timeline, dueTimer, getDueTime, compareTimestamps);
if (index >= 0) {
while (index < this._timeline.length) {
const event = this._timeline[index];
if (event === dueTimer) {
removeAt(this._timeline, index);
dueTimer.timeline = false;
return true;
}
if (event.due > dueTimer.due) {
break;
}
index++;
}
}
}
return false;
}
}
function getDueTime(v: Due<Timer>) {
return v.due;
}
function compareTimestamps(a: number, b: number) {
return a - b;
}
function binarySearch<T, U>(array: ReadonlyArray<T>, value: T, keySelector: (v: T) => U, keyComparer: (a: U, b: U) => number): number {
if (array.length === 0) {
return -1;
}
let low = 0;
let high = array.length - 1;
const key = keySelector(value);
while (low <= high) {
const middle = low + ((high - low) >> 1);
const midKey = keySelector(array[middle]);
const result = keyComparer(midKey, key);
if (result < 0) {
low = middle + 1;
}
else if (result > 0) {
high = middle - 1;
}
else {
return middle;
}
}
return ~low;
}
function removeAt<T>(array: T[], index: number): void {
if (array.length === 0) {
return;
}
else if (index === 0) {
array.shift();
}
else if (index === array.length - 1) {
array.pop();
}
else {
for (let i = index; i < array.length - 1; i++) {
array[i] = array[i + 1];
}
array.length--;
}
}
function insertAt<T>(array: T[], index: number, value: T): void {
if (index === 0) {
array.unshift(value);
}
else if (index === array.length) {
array.push(value);
}
else {
for (let i = array.length; i > index; i--) {
array[i] = array[i - 1];
}
array[index] = value;
}
}

View File

@ -1,120 +0,0 @@
/**
* Defines the number of times an action must have been executed during verification of a Mock.
*/
export class Times {
private static _none: Times | undefined;
private static _once: Times | undefined;
private static _atLeastOnce: Times | undefined;
private static _atMostOnce: Times | undefined;
private _min: number;
private _max: number;
private _message: string;
private constructor(min: number, max: number, message: string) {
this._min = min;
this._max = max;
this._message = message;
}
/**
* Expects that an action was never executed.
* @returns A new `Times` instance.
*/
public static none(): Times {
return this._none || (this._none = new Times(0, 0, `never`));
}
/**
* Expects that an action was executed exactly once.
* @returns A new `Times` instance.
*/
public static once(): Times {
return this._once || (this._once = new Times(1, 1, `exactly once`));
}
/**
* Expects that an action was executed at least once.
* @returns A new `Times` instance.
*/
public static atLeastOnce(): Times {
return this._atLeastOnce || (this._atLeastOnce = new Times(1, Number.MAX_SAFE_INTEGER, `at least once`));
}
/**
* Expects that an action was executed at least the specified number of times.
* @param count The number of times.
* @returns A new `Times` instance.
*/
public static atLeast(count: number): Times {
return new Times(count, Number.MAX_SAFE_INTEGER, `at least ${count} time(s)`);
}
/**
* Expects that an action was executed exactly the specified number of times.
* @param count The number of times.
* @returns A new `Times` instance.
*/
public static exactly(count: number): Times {
return new Times(count, count, `exactly ${count} time(s)`);
}
/**
* Expects that an action was executed at most the specified number of times.
* @param count The number of times.
* @returns A new `Times` instance.
*/
public static atMost(count: number): Times {
return new Times(0, count, `at most ${count} time(s)`);
}
/**
* Expects that an action was executed at most once.
* @returns A new `Times` instance.
*/
public static atMostOnce(): Times {
return this._atMostOnce || (this._atMostOnce = new Times(0, 1, `at most once`));
}
/**
* Expects that an action was executed between a range of times, inclusive.
* @param min The minimum number of times, inclusive.
* @param max The maximum number of times, inclusive.
* @returns A new `Times` instance.
*/
public static between(min: number, max: number): Times {
return new Times(min, max, `between ${min} and ${max} time(s)`);
}
/**
* Validates the number of times an action was executed.
* @param count The number of times the action was executed.
* @returns `true` if the provided count was valid; otherwise, `false`.
*/
public validate(count: number): boolean {
if (count < this._min) return false;
if (count > this._max) return false;
return true;
}
/**
* Checks the number of times an action was executed, throwing an error if the count was not valid.
* @param count The number of times the action was executed.
* @param message The message to use to begin the check.
*/
public check(count: number, message: string): void {
if (!this.validate(count)) {
const expectedMessage = this._message === `never`
? `Expected to never be executed.`
: `Expected to be executed ${this._message}.`;
throw new Error(`${message}\n${expectedMessage} Actually executed ${count} time(s).`);
}
}
/**
* Gets the string representation of this object.
*/
public toString(): string {
return `<${this._message}>`;
}
}

View File

@ -1,17 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"strict": true,
"declaration": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"types": ["mocha"],
"newLine": "LF",
"outDir": "dist"
},
"include": [
"src/**/*"
]
}

View File

@ -27,6 +27,10 @@ export class SortedMap<K, V> {
return this._keys.length;
}
public get comparer() {
return this._comparer;
}
public get [Symbol.toStringTag]() {
return "SortedMap";
}
@ -226,6 +230,10 @@ export class SortedSet<T> {
return this._values.length;
}
public get comparer() {
return this._comparer;
}
public get [Symbol.toStringTag]() {
return "SortedSet";
}

View File

@ -17,7 +17,6 @@
"@types/mocha": "^2.2.44",
"@types/node": "^8.0.20",
"@types/source-map-support": "^0.4.0",
"typemock": "file:../typemock",
"chai": "^4.1.2",
"del": "^2.0.2",
"gulp": "^3.9.1",

View File

@ -1,6 +1,6 @@
import * as vpath from "@typescript/vfs-path";
import * as constants from "../constants";
import { spy, Times, Arg } from "typemock";
import { createSpy } from "./utils";
import { FileSystem } from "../fileSystem";
import { Link, Symlink } from "../fileSet";
import { assert } from "chai";
@ -832,528 +832,579 @@ describe("fileSystem", () => {
describe("directory (non-recursive)", () => {
it("open() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {} } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.openSync("dir/file", "w+");
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"]
]);
});
it("open() + write() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {} } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
const fd = fs.openSync("dir/file", "w+");
fs.writeSync(fd, Buffer.from("test"), 0, 4);
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"]
]);
});
it("open() + write() + close() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {} } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
const fd = fs.openSync("dir/file", "w+");
fs.writeSync(fd, Buffer.from("test"), 0, 4);
fs.closeSync(fd);
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _("change", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"],
["change", "file"]
]);
});
it("open() + close() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {} } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
const fd = fs.openSync("dir/file", "w+");
fs.closeSync(fd);
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"],
["change", "file"]
]);
});
it("writeFile() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {} } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.writeFileSync("dir/file", "test");
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _("change", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"],
["change", "file"]
]);
});
it("writeFile() replace file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "" } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.writeFileSync("dir/file", "test");
callbackSpy.verify(_ => _("change", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["change", "file"]
]);
});
it("truncate() file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "test" } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.truncateSync(vpath.combine(root, "dir/file"));
callbackSpy.verify(_ => _("change", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["change", "file"]
]);
});
it("rename() file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "test" } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.renameSync(vpath.combine(root, "dir/file"), vpath.combine(root, "dir/file1"));
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _("rename", "file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"],
["rename", "file1"]
]);
});
it("link() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "test" } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.linkSync(vpath.combine(root, "dir/file"), vpath.combine(root, "dir/file1"));
callbackSpy.verify(_ => _("rename", "file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file1"]
]);
});
it("symlink() file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "test" } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.symlinkSync(vpath.combine(root, "dir/file"), vpath.combine(root, "dir/file1"));
callbackSpy.verify(_ => _("rename", "file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file1"]
]);
});
it("unlink() file (single link)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "" } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.unlinkSync(vpath.combine(root, "dir/file"));
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"]
]);
});
it("unlink() file (multiple links)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "", "file1": new Link("file") } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.unlinkSync(vpath.combine(root, "dir/file"));
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"]
]);
});
it("mkdir() new subdirectory", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.mkdirSync(vpath.combine(root, "dir/subdir"));
callbackSpy.verify(_ => _("rename", "subdir"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "subdir"]
]);
});
it("rmdir() subdirectory", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "subdir": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), callbackSpy.proxy);
fs.rmdirSync(vpath.combine(root, "dir/subdir"));
callbackSpy.verify(_ => _("rename", "subdir"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "subdir"]
]);
});
});
describe("directory (recursive)", () => {
it("open() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.openSync("dir/sub1/file1", "w+");
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["change", "sub1"]
]);
});
it("open() + write() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
const fd = fs.openSync("dir/sub1/file1", "w+");
fs.writeSync(fd, Buffer.from("test"), 0, 4);
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["change", "sub1"]
]);
});
it("open() + write() + close() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
const fd = fs.openSync("dir/sub1/file1", "w+");
fs.writeSync(fd, Buffer.from("test"), 0, 4);
fs.closeSync(fd);
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(3));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["change", "sub1/file1"],
["change", "sub1"]
]);
});
it("open() + close() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
const fd = fs.openSync("dir/sub1/file1", "w+");
fs.closeSync(fd);
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["change", "sub1"],
["change", "sub1/file1"]
]);
});
it("writeFile() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.writeFileSync("dir/sub1/file1", "test");
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(3));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["change", "sub1/file1"],
["change", "sub1"]
]);
});
it("writeFile() replace file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file1": "" } } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.writeFileSync("dir/sub1/file1", "test");
callbackSpy.verify(_ => _("change", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["change", "sub1/file1"]
]);
});
it("truncate() file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file1": "test" } } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.truncateSync(vpath.combine(root, "dir/sub1/file1"));
callbackSpy.verify(_ => _("change", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["change", "sub1/file1"]
]);
});
it("rename() file (same directory)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file1": "test" } } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.renameSync(vpath.combine(root, "dir/sub1/file1"), vpath.combine(root, "dir/sub1/file2"));
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("rename", "sub1/file2"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(3));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["rename", "sub1/file2"],
["change", "sub1"]
]);
});
it("rename() file (different directory)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file1": "test" }, "sub2": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.renameSync(vpath.combine(root, "dir/sub1/file1"), vpath.combine(root, "dir/sub2/file1"));
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("rename", "sub2/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _("change", "sub2"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(4));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["rename", "sub2/file1"],
["change", "sub1"],
["change", "sub2"]
]);
});
it("link() file (same directory)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file1": "test" } } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.linkSync(vpath.combine(root, "dir/sub1/file1"), vpath.combine(root, "dir/sub1/file2"));
callbackSpy.verify(_ => _("rename", "sub1/file2"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file2"],
["change", "sub1"]
]);
});
it("link() file (different directory)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file1": "test" }, "sub2": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.linkSync(vpath.combine(root, "dir/sub1/file1"), vpath.combine(root, "dir/sub2/file1"));
callbackSpy.verify(_ => _("rename", "sub2/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub2"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub2/file1"],
["change", "sub2"]
]);
});
it("symlink() file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file": "test" } } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.symlinkSync(vpath.combine(root, "dir/sub1/file"), vpath.combine(root, "dir/sub1/file1"));
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["change", "sub1"]
]);
});
it("unlink() file (single link)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file1": "" } } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.unlinkSync(vpath.combine(root, "dir/sub1/file1"));
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["change", "sub1"]
]);
});
it("unlink() file (multiple links)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "file1": "", "file2": new Link("file1") } } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.unlinkSync(vpath.combine(root, "dir/sub1/file1"));
callbackSpy.verify(_ => _("rename", "sub1/file1"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/file1"],
["change", "sub1"]
]);
});
it("mkdir() new subdirectory", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": {} } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.mkdirSync(vpath.combine(root, "dir/sub1/sub2"));
callbackSpy.verify(_ => _("rename", "sub1/sub2"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/sub2"],
["change", "sub1"]
]);
});
it("rmdir() subdirectory", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "sub1": { "sub2": {} } } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir"), { recursive: true }, callbackSpy.proxy);
fs.rmdirSync(vpath.combine(root, "dir/sub1/sub2"));
callbackSpy.verify(_ => _("rename", "sub1/sub2"), Times.once());
callbackSpy.verify(_ => _("change", "sub1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "sub1/sub2"],
["change", "sub1"]
]);
});
});
describe("file", () => {
it("writeFile() replace", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file1": "" } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir/file1"), callbackSpy.proxy);
fs.writeFileSync("dir/file1", "test");
callbackSpy.verify(_ => _("change", "file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["change", "file1"]
]);
});
it("unlink() (single link)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file1": "" } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir/file1"), callbackSpy.proxy);
fs.unlinkSync(vpath.combine(root, "dir/file1"));
callbackSpy.verify(_ => _("rename", "file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file1"]
]);
});
});
describe("symlink (directory, non-recursive)", () => {
it("open() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {}, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.openSync("dir/file", "w+");
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"]
]);
});
it("open() + write() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {}, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
const fd = fs.openSync("dir/file", "w+");
fs.writeSync(fd, Buffer.from("test"), 0, 4);
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"]
]);
});
it("open() + write() + close() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {}, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
const fd = fs.openSync("dir/file", "w+");
fs.writeSync(fd, Buffer.from("test"), 0, 4);
fs.closeSync(fd);
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _("change", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"],
["change", "file"]
]);
});
it("open() + close() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {}, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
const fd = fs.openSync("dir/file", "w+");
fs.closeSync(fd);
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"],
["change", "file"]
]);
});
it("writeFile() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": {}, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.writeFileSync("dir/file", "test");
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _("change", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"],
["change", "file"]
]);
});
it("writeFile() replace file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "" }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.writeFileSync("dir/file", "test");
callbackSpy.verify(_ => _("change", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["change", "file"]
]);
});
it("truncate() file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "test" }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.truncateSync(vpath.combine(root, "dir/file"));
callbackSpy.verify(_ => _("change", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["change", "file"]
]);
});
it("rename() file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "test" }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.renameSync(vpath.combine(root, "dir/file"), vpath.combine(root, "dir/file1"));
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _("rename", "file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.exactly(2));
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"],
["rename", "file1"]
]);
});
it("link() new file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "test" }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.linkSync(vpath.combine(root, "dir/file"), vpath.combine(root, "dir/file1"));
callbackSpy.verify(_ => _("rename", "file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file1"]
]);
});
it("symlink() file", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "test" }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.symlinkSync(vpath.combine(root, "dir/file"), vpath.combine(root, "dir/file1"));
callbackSpy.verify(_ => _("rename", "file1"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file1"]
]);
});
it("unlink() file (single link)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "" }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.unlinkSync(vpath.combine(root, "dir/file"));
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"]
]);
});
it("unlink() file (multiple links)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file": "", "file1": new Link("file") }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.unlinkSync(vpath.combine(root, "dir/file"));
callbackSpy.verify(_ => _("rename", "file"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file"]
]);
});
it("mkdir() new subdirectory", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.mkdirSync(vpath.combine(root, "dir/subdir"));
callbackSpy.verify(_ => _("rename", "subdir"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "subdir"]
]);
});
it("rmdir() subdirectory", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "subdir": {} }, "dir2": new Symlink("dir") } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir2"), callbackSpy.proxy);
fs.rmdirSync(vpath.combine(root, "dir/subdir"));
callbackSpy.verify(_ => _("rename", "subdir"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "subdir"]
]);
});
});
describe("symlink (file)", () => {
it("writeFile() replace", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file1": "", "file2": new Symlink("file1") } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir/file2"), callbackSpy.proxy);
fs.writeFileSync("dir/file1", "test");
callbackSpy.verify(_ => _("change", "file2"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["change", "file2"]
]);
});
it("unlink() (single link)", () => {
const fs = new FileSystem(ignoreCase, { files: { [root]: { "dir": { "file1": "", "file2": new Symlink("file1") } } } });
const callbackSpy = spy<(eventType: string, filename: string) => void>();
const callbackSpy = createSpy();
fs.watch(vpath.combine(root, "dir/file2"), callbackSpy.proxy);
fs.unlinkSync(vpath.combine(root, "dir/file1"));
callbackSpy.verify(_ => _("rename", "file2"), Times.once());
callbackSpy.verify(_ => _(Arg.any(), Arg.any()), Times.once());
assert.sameDeepMembers(callbackSpy.calls, [
["rename", "file2"]
]);
});
});
it("fail: invalid path (ENOENT)", () => {

View File

@ -11,4 +11,10 @@ export function theory(name: string, data: (Theory | any[])[], callback: (...arg
it(title, () => callback(...args));
}
});
}
export function createSpy() {
const calls: any[][] = [];
const proxy = function(...args: any[]): any { calls.push(args); }
return { proxy, calls };
}

View File

@ -1,145 +1,64 @@
/// <reference path="./core.ts" />
/// <reference path="./assert.ts" />
/// <reference path="./utils.ts" />
/// <reference path="./vfs.ts" />
/// <reference path="./typemock.ts" />
// 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 fakes
namespace fakes {
export interface FakeServerHostOptions {
/**
* The `VirtualFleSystem` to use. If not specified, a new case-sensitive `VirtualFileSystem`
* is created.
*/
vfs?: vfs.FileSystem | { currentDirectory?: string, useCaseSensitiveFileNames?: boolean };
const processExitSentinel = new Error("System exit");
export interface ServerHostOptions {
/**
* The virtual path to tsc.js. If not specified, a default of `"/.ts/tsc.js"` is used.
*/
executingFilePath?: string;
/**
* The new-line style. If not specified, a default of `"\n"` is used.
*/
newLine?: "\r\n" | "\n";
/**
* Indicates whether to include _safeList.json_.
*/
safeList?: boolean;
/**
* Indicates whether to include a bare _lib.d.ts_.
*/
lib?: boolean;
/**
* Indicates whether to use DOS paths by default.
*/
dos?: boolean;
}
export class FakeServerHost implements ts.server.ServerHost, ts.FormatDiagnosticsHost, ts.ModuleResolutionHost {
public static readonly dosExecutingFilePath = vpath.combine(vfsutils.dosBuiltFolder, "tsc.js");
public static readonly defaultExecutingFilePath = vpath.combine(vfsutils.builtFolder, "tsc.js");
public static readonly dosDefaultCurrentDirectory = "c:/";
public static readonly defaultCurrentDirectory = "/";
public static readonly dosSafeListPath = "c:/safelist.json";
public static readonly safeListPath = "/safelist.json";
public static readonly safeListContent =
`{\n` +
` "commander": "commander",\n` +
` "express": "express",\n` +
` "jquery": "jquery",\n` +
` "lodash": "lodash",\n` +
` "moment": "moment",\n` +
` "chroma": "chroma-js"\n` +
`}`;
public static readonly dosLibPath = vpath.combine(vfsutils.dosBuiltFolder, "lib.d.ts");
public static readonly libPath = vpath.combine(vfsutils.builtFolder, "lib.d.ts");
public static readonly libContent =
`/// <reference no-default-lib="true"/>\n` +
`interface Boolean {}\n` +
`interface Function {}\n` +
`interface IArguments {}\n` +
`interface Number { toExponential: any; }\n` +
`interface Object {}\n` +
`interface RegExp {}\n` +
`interface String { charAt: any; }\n` +
`interface Array<T> {}`;
public readonly timers = new typemock.Timers();
export class ServerHost implements ts.server.ServerHost, ts.FormatDiagnosticsHost {
public readonly vfs: vfs.FileSystem;
public exitCode: number;
public watchFiles = true;
public watchDirectories = true;
private static readonly processExitSentinel = new Error("System exit");
private readonly _output: string[] = [];
private readonly _trace: string[] = [];
private readonly _executingFilePath: string;
private readonly _getCanonicalFileName: (file: string) => string;
private _screenClears = 0;
private _watchedFiles: core.SortedMap<string, number> | undefined;
private _watchedFilesSet: core.SortedSet<string> | undefined;
private _watchedRecursiveDirectories: core.SortedMap<string, number> | undefined;
private _watchedRecursiveDirectoriesSet: core.SortedSet<string> | undefined;
private _watchedNonRecursiveDirectories: core.SortedMap<string, number> | undefined;
private _watchedNonRecursiveDirectoriesSet: core.SortedSet<string> | undefined;
constructor(options: FakeServerHostOptions = {}, files?: vfs.FileSet) {
constructor(vfs: vfs.FileSystem, options: ServerHostOptions = {}) {
const {
dos = false,
vfs: _vfs = {},
executingFilePath = dos
? FakeServerHost.dosExecutingFilePath
: FakeServerHost.defaultExecutingFilePath,
? vfsutils.dosTscPath
: vfsutils.tscPath,
newLine = "\n",
safeList = false,
lib = false,
} = options;
const currentDirectory = _vfs instanceof vfs.FileSystem ? _vfs.cwd() :
_vfs.currentDirectory !== undefined ? _vfs.currentDirectory :
dos ? FakeServerHost.dosDefaultCurrentDirectory :
FakeServerHost.defaultCurrentDirectory;
const useCaseSensitiveFileNames = _vfs instanceof vfs.FileSystem ? !_vfs.ignoreCase :
_vfs.useCaseSensitiveFileNames !== undefined ? _vfs.useCaseSensitiveFileNames :
false;
this.vfs = _vfs instanceof vfs.FileSystem
? _vfs
: new vfs.FileSystem(!useCaseSensitiveFileNames, { cwd: currentDirectory });
if (this.vfs.isReadonly) {
this.vfs = this.vfs.shadow();
}
this.vfs.mkdirpSync(currentDirectory);
this.vfs.chdir(currentDirectory);
this.vfs = vfs.isReadonly ? vfs.shadow() : vfs;
this.useCaseSensitiveFileNames = !this.vfs.ignoreCase;
this.newLine = newLine;
this._executingFilePath = executingFilePath;
this._getCanonicalFileName = ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames);
if (files) {
this.vfs.apply(files);
}
if (safeList) {
const safeListPath = dos ? FakeServerHost.dosSafeListPath : FakeServerHost.safeListPath;
this.vfs.mkdirpSync(vpath.dirname(safeListPath));
this.vfs.writeFileSync(safeListPath, FakeServerHost.safeListContent);
const safelistPath = dos ? vfsutils.dosSafelistPath : vfsutils.safelistPath;
this.vfs.mkdirpSync(vpath.dirname(safelistPath));
this.vfs.writeFileSync(safelistPath, vfsutils.safelistContent);
}
if (lib) {
const libPath = dos ? FakeServerHost.dosLibPath : FakeServerHost.libPath;
const libPath = dos ? vfsutils.dosLibPath : vfsutils.libPath;
this.vfs.mkdirpSync(vpath.dirname(libPath));
this.vfs.writeFileSync(libPath, FakeServerHost.libContent);
this.vfs.writeFileSync(libPath, vfsutils.emptyLibContent);
}
}
// #region DirectoryStructureHost members
// #region System members
public readonly newLine: string;
public readonly useCaseSensitiveFileNames: boolean;
@ -183,17 +102,9 @@ namespace fakes {
public exit(exitCode?: number) {
this.exitCode = exitCode;
throw FakeServerHost.processExitSentinel;
throw processExitSentinel;
}
// #endregion DirectoryStructureHost members
// #region ModuleResolutionHost members
public trace(message: string) {
this._trace.push(message);
}
// #endregion
// #region System members
public readonly args: string[] = [];
public getFileSize(path: string) {
@ -201,77 +112,11 @@ namespace fakes {
}
public watchFile(path: string, cb: ts.FileWatcherCallback) {
if (!this._watchedFiles) this._watchedFiles = new core.SortedMap<string, number>({ comparer: this.vfs.stringComparer, sort: "insertion" });
if (!this._watchedFilesSet) this._watchedFilesSet = new core.SortedSet<string>(this.vfs.stringComparer);
const previousCount = this._watchedFiles.get(path) || 0;
if (previousCount === 0) {
this._watchedFilesSet.add(path);
}
this._watchedFiles.set(path, previousCount + 1);
let watching = true;
const watcher = vfsutils.watchFile(this.vfs, path, (fileName, eventKind) => {
if (this.watchFiles) cb(fileName, eventKind);
});
return {
close: () => {
if (watching) {
const previousCount = this._watchedFiles.get(path) || 0;
if (previousCount === 1) {
this._watchedFiles.delete(path);
this._watchedFilesSet.delete(path);
}
else {
this._watchedFiles.set(path, previousCount - 1);
}
watcher.close();
watching = false;
}
}
};
return vfsutils.watchFile(this.vfs, path, cb);
}
public watchDirectory(path: string, cb: ts.DirectoryWatcherCallback, recursive?: boolean): ts.FileWatcher {
const watchedDirectories = recursive
? this._watchedRecursiveDirectories || (this._watchedRecursiveDirectories = new core.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" }))
: this._watchedNonRecursiveDirectories || (this._watchedNonRecursiveDirectories = new core.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" }));
const watchedDirectoriesSet = recursive
? this._watchedRecursiveDirectoriesSet || (this._watchedRecursiveDirectoriesSet = new core.SortedSet(this.vfs.stringComparer))
: this._watchedNonRecursiveDirectoriesSet || (this._watchedNonRecursiveDirectoriesSet = new core.SortedSet(this.vfs.stringComparer));
const previousCount = watchedDirectories.get(path) || 0;
if (previousCount === 0) {
watchedDirectoriesSet.add(path);
}
watchedDirectories.set(path, previousCount + 1);
let watcher: ts.FileWatcher | undefined = vfsutils.watchDirectory(this.vfs, path, fileName => {
if (this.watchDirectories) {
cb(fileName);
}
}, recursive);
return {
close: () => {
if (watcher) {
const previousCount = watchedDirectories.get(path) || 0;
if (previousCount === 1) {
watchedDirectories.delete(path);
watchedDirectoriesSet.delete(path);
}
else {
watchedDirectories.set(path, previousCount - 1);
}
watcher.close();
watcher = undefined;
}
}
};
return vfsutils.watchDirectory(this.vfs, path, cb, recursive);
}
public resolvePath(path: string) {
@ -303,16 +148,12 @@ namespace fakes {
return undefined;
}
public setTimeout(callback: (...args: any[]) => void, timeout: number, ...args: any[]) {
return this.timers.setTimeout(callback, timeout, ...args);
public setTimeout(_callback: (...args: any[]) => void, _timeout: number, ..._args: any[]) {
return ts.notImplemented();
}
public clearTimeout(timeoutId: any): void {
this.timers.clearTimeout(timeoutId);
}
public clearScreen(): void {
this._screenClears++;
public clearTimeout(_timeoutId: any): void {
return ts.notImplemented();
}
// #endregion System members
@ -327,105 +168,14 @@ namespace fakes {
// #endregion FormatDiagnosticsHost members
// #region ServerHost members
public setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
return this.timers.setImmediate(callback, args);
public setImmediate(_callback: (...args: any[]) => void, ..._args: any[]): any {
return ts.notImplemented();
}
public clearImmediate(timeoutId: any): void {
this.timers.clearImmedate(timeoutId);
public clearImmediate(_timeoutId: any): void {
return ts.notImplemented();
}
// #endregion ServerHost members
public getOutput(): ReadonlyArray<string> {
return this._output;
}
public clearOutput() {
this._output.length = 0;
}
public getTrace(): ReadonlyArray<string> {
return this._trace;
}
public clearTrace() {
this._trace.length = 0;
}
// expectations
public checkTimeoutQueueLength(expected: number) {
const callbacksCount = this.timers.getPending({ kind: "timeout", ms: this.timers.remainingTime }).length;
assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`);
}
public checkTimeoutQueueLengthAndRun(count: number) {
this.checkTimeoutQueueLength(count);
this.runQueuedTimeoutCallbacks();
}
public runQueuedImmediateCallbacks() {
try {
this.timers.executeImmediates();
}
catch (e) {
if (e !== FakeServerHost.processExitSentinel) {
throw e;
}
}
}
public runQueuedTimeoutCallbacks() {
try {
this.timers.advanceToEnd();
}
catch (e) {
if (e !== FakeServerHost.processExitSentinel) {
throw e;
}
}
}
public checkOutputContains(expected: Iterable<string>) {
const mapExpected = new Set(expected);
const mapSeen = new Set<string>();
for (const f of this._output) {
assert.isFalse(mapSeen.has(f), `Already found ${f} in ${JSON.stringify(this._output)}`);
if (mapExpected.has(f)) {
mapExpected.delete(f);
mapSeen.add(f);
}
}
assert.equal(mapExpected.size, 0, `Output is missing ${JSON.stringify(ts.flatMap(Array.from(mapExpected.keys()), key => key))} in ${JSON.stringify(this._output)}`);
}
public checkOutputDoesNotContain(notExpected: Iterable<string>) {
const mapExpectedToBeAbsent = new Set(notExpected);
for (const f of this._output) {
assert.isFalse(mapExpectedToBeAbsent.has(f), `Contains ${f} in ${JSON.stringify(this._output)}`);
}
}
public checkWatchedFiles(expected: Iterable<string>) {
return checkSortedSet(this._watchedFilesSet, expected);
}
public checkWatchedDirectories(expected: Iterable<string>, recursive = false) {
return checkSortedSet(recursive ? this._watchedRecursiveDirectoriesSet : this._watchedNonRecursiveDirectoriesSet, expected);
}
public checkScreenClears(expected: number) {
assert.equal(this._screenClears, expected);
}
}
}
function checkSortedSet<T>(set: ReadonlySet<T> | undefined, values: Iterable<T>) {
const array = Array.from(values);
const size = set ? set.size : 0;
assert.strictEqual(size, array.length, `Actual: ${set ? Array.from(set) : []}, expected: ${array}.`);
if (set) {
for (const value of array) {
assert.isTrue(set.has(value));
}
}
}
}

View File

@ -73,10 +73,10 @@
"../services/codefixes/disableJsDiagnostics.ts",
"harness.ts",
"assert.ts",
"core.ts",
"utils.ts",
"typemock.ts",
"events.ts",
"documents.ts",
"vpath.ts",
@ -101,6 +101,7 @@
"./parallel/host.ts",
"./parallel/worker.ts",
"runner.ts",
"virtualFileSystemWithWatch.ts",
"../server/protocol.ts",
"../server/session.ts",
"../server/client.ts",

View File

@ -1,41 +0,0 @@
// NOTE: This namespace re-exports all of the exports from the typemock private package.
// typemock library
namespace typemock {
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;
}
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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -111,7 +111,7 @@ namespace ts {
function runBaseline(extension: Extension) {
const path = "/a" + extension;
const program = makeProgram({ path, content: t.source }, includeLib);
const program = makeProgram(path, t.source, includeLib);
if (hasSyntacticDiagnostics(program)) {
// Don't bother generating JS baselines for inputs that aren't valid JS.
@ -146,18 +146,19 @@ namespace ts {
const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
data.push(newTextWithRename);
const diagProgram = makeProgram({ path, content: newText }, includeLib);
const diagProgram = makeProgram(path, newText, includeLib);
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
}
return data.join(newLineCharacter);
});
}
function makeProgram(f: {path: string, content: string }, includeLib?: boolean) {
function makeProgram(path: string, content: string, includeLib?: boolean) {
// libFile is expensive to parse repeatedly - only test when required
const host = new fakes.FakeServerHost({ lib: includeLib }, /*files*/ { [f.path]: f.content });
const fs = new vfs.FileSystem(/*ignoreCase*/ true, { files: { [path]: content } });
const host = new fakes.ServerHost(fs, { lib: includeLib });
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile(f.path);
projectService.openClientFile(path);
const program = projectService.inferredProjects[0].getLanguageService().getProgram();
return program;
}
@ -175,7 +176,8 @@ namespace ts {
if (!selectionRange) {
throw new Error(`Test ${caption} does not specify selection range`);
}
const host = new fakes.FakeServerHost({ lib: true }, /*files*/ { "/a.ts": t.source });
const fs = new vfs.FileSystem(/*ignoreCase*/ true, { files: { "/a.ts": t.source } });
const host = new fakes.ServerHost(fs, { lib: true });
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile("/a.ts");
const program = projectService.inferredProjects[0].getLanguageService().getProgram();

View File

@ -1,7 +1,6 @@
/// <reference path="../harness.ts" />
/// <reference path="./tsserverProjectSystem.ts" />
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
/// <reference path="../fakes.ts" />
namespace ts.projectSystem {
describe("Project errors", () => {
@ -31,131 +30,177 @@ namespace ts.projectSystem {
}
it("external project - diagnostics for missing files", () => {
const host = new fakes.FakeServerHost({ safeList: true, lib: true }, /*files*/ {
"/a/b/app.ts": ``,
});
const projectFileName = "/a/b/test.csproj";
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/applib.ts",
content: ""
};
const host = createServerHost([file1, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles(["/a/b/app.ts", "/a/b/applib.ts"])
});
const projectFileName = "/a/b/test.csproj";
const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = {
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
seq: 2,
arguments: { projectFileName }
};
// only file1 exists - expect error
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags1 = sendCompilerOptionsDiagnosticsRequest(session, { projectFileName }, /*seq*/ 2);
checkDiagnosticsWithLinePos(diags1, ["File '/a/b/applib.ts' not found."]);
{
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles([file1.path, file2.path])
});
host.vfs.unlinkSync("/a/b/app.ts");
host.vfs.writeFileSync("/a/b/applib.ts", ``);
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
// only file1 exists - expect error
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
}
host.reloadFS([file2, libFile]);
{
// only file2 exists - expect error
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]);
}
// only file2 exists - expect error
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags2 = sendCompilerOptionsDiagnosticsRequest(session, { projectFileName }, /*seq*/ 2);
checkDiagnosticsWithLinePos(diags2, ["File '/a/b/app.ts' not found."]);
host.vfs.writeFileSync("/a/b/app.ts", ``);
// both files exist - expect no errors
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags3 = sendCompilerOptionsDiagnosticsRequest(session, { projectFileName }, /*seq*/ 2);
checkDiagnosticsWithLinePos(diags3, []);
host.reloadFS([file1, file2, libFile]);
{
// both files exist - expect no errors
checkNumberOfProjects(projectService, { externalProjects: 1 });
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, []);
}
});
it("configured projects - diagnostics for missing files", () => {
const host = new fakes.FakeServerHost({ safeList: true, lib: true }, /*files*/ {
"/a/b/app.ts": ``,
"/a/b/tsconfig.json": `{ "files": ["app.ts", "applib.ts"] }`,
});
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/applib.ts",
content: ""
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const host = createServerHost([file1, config, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
openFilesForSession(["/a/b/app.ts"], session);
openFilesForSession([file1], session);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = configuredProjectAt(projectService, 0);
const diags1 = sendCompilerOptionsDiagnosticsRequest(session, { projectFileName: project.getProjectName() }, /*seq*/ 2);
checkDiagnosticsWithLinePos(diags1, ["File '/a/b/applib.ts' not found."]);
const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = {
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
seq: 2,
arguments: { projectFileName: project.getProjectName() }
};
let diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
host.vfs.writeFileSync("/a/b/applib.ts", ``);
host.reloadFS([file1, file2, config, libFile]);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const diags2 = sendCompilerOptionsDiagnosticsRequest(session, { projectFileName: project.getProjectName() }, /*seq*/ 2);
checkDiagnosticsWithLinePos(diags2, []);
diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
checkDiagnosticsWithLinePos(diags, []);
});
it("configured projects - diagnostics for corrupted config 1", () => {
const host = new fakes.FakeServerHost({ safeList: true }, /*files*/ {
"/a/b/app.ts": ``,
"/a/b/lib.ts": ``,
"/a/b/tsconfig.json": ` "files": ["app.ts", "lib.ts"] }`,
});
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const correctConfig = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const corruptedConfig = {
path: correctConfig.path,
content: correctConfig.content.substr(1)
};
const host = createServerHost([file1, file2, corruptedConfig]);
const projectService = createProjectService(host);
projectService.openClientFile("/a/b/app.ts");
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject1 = find(projectService.synchronizeProjectList([]), f => f.info.projectName === "/a/b/tsconfig.json");
assert.isTrue(configuredProject1 !== undefined, "should find configured project");
checkProjectErrors(configuredProject1, []);
const projectErrors1 = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors1, ["'{' expected."]);
assert.isNotNull(projectErrors1[0].file);
assert.equal(projectErrors1[0].file.fileName, "/a/b/tsconfig.json");
projectService.openClientFile(file1.path);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
assert.isNotNull(projectErrors[0].file);
assert.equal(projectErrors[0].file.fileName, corruptedConfig.path);
}
// fix config and trigger watcher
host.vfs.writeFileSync("/a/b/tsconfig.json", `{ "files": ["app.ts", "lib.ts"] }`);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject2 = find(projectService.synchronizeProjectList([]), f => f.info.projectName === "/a/b/tsconfig.json");
assert.isTrue(configuredProject2 !== undefined, "should find configured project");
checkProjectErrors(configuredProject2, []);
const projectErrors2 = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors2, []);
host.reloadFS([file1, file2, correctConfig]);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
});
it("configured projects - diagnostics for corrupted config 2", () => {
const host = new fakes.FakeServerHost({ safeList: true }, /*files*/ {
"/a/b/app.ts": ``,
"/a/b/lib.ts": ``,
"/a/b/tsconfig.json": `{ "files": ["app.ts", "lib.ts"] }`,
});
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const correctConfig = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const corruptedConfig = {
path: correctConfig.path,
content: correctConfig.content.substr(1)
};
const host = createServerHost([file1, file2, correctConfig]);
const projectService = createProjectService(host);
projectService.openClientFile("/a/b/app.ts");
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject1 = find(projectService.synchronizeProjectList([]), f => f.info.projectName === "/a/b/tsconfig.json");
assert.isTrue(configuredProject1 !== undefined, "should find configured project");
checkProjectErrors(configuredProject1, []);
const projectErrors1 = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors1, []);
projectService.openClientFile(file1.path);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
// break config and trigger watcher
host.vfs.writeFileSync("/a/b/tsconfig.json", ` "files": ["app.ts", "lib.ts"] }`);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject2 = find(projectService.synchronizeProjectList([]), f => f.info.projectName === "/a/b/tsconfig.json");
assert.isTrue(configuredProject2 !== undefined, "should find configured project");
checkProjectErrors(configuredProject2, []);
const projectErrors2 = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors2, ["'{' expected."]);
assert.isNotNull(projectErrors2[0].file);
assert.equal(projectErrors2[0].file.fileName, "/a/b/tsconfig.json");
host.reloadFS([file1, file2, corruptedConfig]);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
assert.isNotNull(projectErrors[0].file);
assert.equal(projectErrors[0].file.fileName, corruptedConfig.path);
}
});
});
}

View File

@ -1,6 +1,5 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\harness\harnessLanguageService.ts" />
/// <reference path="../fakes.ts" />
namespace ts {
@ -885,6 +884,10 @@ namespace ts {
});
});
type FileOrFolder = ts.TestFSWithWatch.FileOrFolder;
import createTestSystem = ts.TestFSWithWatch.createWatchedSystem;
import libFile = ts.TestFSWithWatch.libFile;
describe("isProgramUptoDate should return true when there is no change in compiler options and", () => {
function verifyProgramIsUptoDate(
program: Program,
@ -917,37 +920,48 @@ namespace ts {
verifyProgramIsUptoDate(program, fileNames, options);
}
function verifyProgram(vfs: vfs.FileSystem, rootFiles: string[], options: CompilerOptions, configFile: string) {
const system = new fakes.FakeServerHost({ vfs });
function verifyProgram(files: FileOrFolder[], rootFiles: string[], options: CompilerOptions, configFile: string) {
const system = createTestSystem(files);
verifyProgramWithoutConfigFile(system, rootFiles, options);
verifyProgramWithConfigFile(system, configFile);
}
it("has empty options", () => {
const file1 = "/a/b/file1.ts";
const file2 = "/a/b/file2.ts";
const configFile = "/a/b/tsconfig.json";
const fs = new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/", files: {
[file1]: "let x = 1",
[file2]: "let y = 1",
[configFile]: "{}",
[fakes.FakeServerHost.libPath]: fakes.FakeServerHost.libContent,
}});
verifyProgram(fs, [file1, file2], {}, configFile);
const file1: FileOrFolder = {
path: "/a/b/file1.ts",
content: "let x = 1"
};
const file2: FileOrFolder = {
path: "/a/b/file2.ts",
content: "let y = 1"
};
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: "{}"
};
verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path);
});
it("has lib specified in the options", () => {
const app = "/src/app.ts";
const configFile = "/src/tsconfig.json";
const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] };
const fs = new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/", files: {
[app]: "var x: Promise<string>;",
[configFile]: JSON.stringify({ compilerOptions }),
"/compiler/lib.es5.d.ts": "declare const eval: any;",
"/compiler/lib.es2015.promise.d.ts": "declare class Promise<T> {}",
}});
verifyProgram(fs, [app], compilerOptions, configFile);
const app: FileOrFolder = {
path: "/src/app.ts",
content: "var x: Promise<string>;"
};
const configFile: FileOrFolder = {
path: "/src/tsconfig.json",
content: JSON.stringify({ compilerOptions })
};
const es5Lib: FileOrFolder = {
path: "/compiler/lib.es5.d.ts",
content: "declare const eval: any"
};
const es2015Promise: FileOrFolder = {
path: "/compiler/lib.es2015.promise.d.ts",
content: "declare class Promise<T> {}"
};
verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path);
});
it("has paths specified in the options", () => {
@ -961,26 +975,31 @@ namespace ts {
]
}
};
const app = "/src/packages/framework/app.ts";
const configFile = "/src/tsconfig.json";
const fs = new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/", files: {
[app]:
`import classC from "module1/lib/file1";\n` +
`import classD from "module3/file3";\n` +
`let x = new classC();\n` +
`let y = new classD();`,
"/src/packages/mail/data/module1/lib/file1.ts":
`import classC from "module2/file2";\n` +
`export default classC;`,
"/src/packages/mail/data/module1/lib/module2/file2.ts":
`class classC { method2() { return "hello"; } }\n` +
`export default classC;`,
"/src/packages/styles/module3/file3.ts":
`class classD { method() { return 10; } }\n` +
`export default classD;`,
[configFile]: JSON.stringify({ compilerOptions }),
}});
verifyProgram(fs, [app], compilerOptions, configFile);
const app: FileOrFolder = {
path: "/src/packages/framework/app.ts",
content: 'import classc from "module1/lib/file1";\
import classD from "module3/file3";\
let x = new classc();\
let y = new classD();'
};
const module1: FileOrFolder = {
path: "/src/packages/mail/data/module1/lib/file1.ts",
content: 'import classc from "module2/file2";export default classc;',
};
const module2: FileOrFolder = {
path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
content: 'class classc { method2() { return "hello"; } }\nexport default classc',
};
const module3: FileOrFolder = {
path: "/src/packages/styles/module3/file3.ts",
content: "class classD { method() { return 10; } }\nexport default classD;"
};
const configFile: FileOrFolder = {
path: "/src/tsconfig.json",
content: JSON.stringify({ compilerOptions })
};
verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path);
});
it("has include paths specified in tsconfig file", () => {
@ -994,27 +1013,30 @@ namespace ts {
]
}
};
const configFile = "/src/tsconfig.json";
const fs = new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/", files: {
"/src/packages/framework/app.ts":
`import classC from "module1/lib/file1";\n` +
`import classD from "module3/file3";\n` +
`let x = new classC();\n` +
`let y = new classD();`,
"/src/packages/mail/data/module1/lib/file1.ts":
`import classC from "module2/file2";\n` +
`export default classC;`,
"/src/packages/mail/data/module1/lib/module2/file2.ts":
`class classC { method2() { return "hello"; } }\n` +
`export default classC;`,
"/src/packages/styles/module3/file3.ts":
`class classD { method() { return 10; } }\n` +
`export default classD;`,
[configFile]:
JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] }),
}});
const watchingSystemHost = new fakes.FakeServerHost({ vfs: fs });
verifyProgramWithConfigFile(watchingSystemHost, configFile);
const app: FileOrFolder = {
path: "/src/packages/framework/app.ts",
content: 'import classc from "module1/lib/file1";\
import classD from "module3/file3";\
let x = new classc();\
let y = new classD();'
};
const module1: FileOrFolder = {
path: "/src/packages/mail/data/module1/lib/file1.ts",
content: 'import classc from "module2/file2";export default classc;',
};
const module2: FileOrFolder = {
path: "/src/packages/mail/data/module1/lib/module2/file2.ts",
content: 'class classc { method2() { return "hello"; } }\nexport default classc',
};
const module3: FileOrFolder = {
path: "/src/packages/styles/module3/file3.ts",
content: "class classD { method() { return 10; } }\nexport default classD;"
};
const configFile: FileOrFolder = {
path: "/src/tsconfig.json",
content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] })
};
verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path);
});
});
}

View File

@ -1,59 +1,46 @@
/// <reference path="../harness.ts" />
/// <reference path="./tsserverProjectSystem.ts" />
/// <reference path="../fakes.ts" />
namespace ts.projectSystem {
describe("project telemetry", () => {
it("does nothing for inferred project", () => {
const host = new fakes.FakeServerHost({}, /*files*/ {
"/a.js": "",
});
const et = new TestServerEventManager(host);
et.service.openClientFile("/a.js");
const file = makeFile("/a.js");
const et = new TestServerEventManager([file]);
et.service.openClientFile(file.path);
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
});
it("only sends an event once", () => {
const host = new fakes.FakeServerHost({}, /*files*/ {
"/a/a.ts": ``,
"/b.ts": ``,
"/a/tsconfig.json": `{}`,
});
const file = makeFile("/a/a.ts");
const file2 = makeFile("/b.ts");
const tsconfig = makeFile("/a/tsconfig.json", {});
const et = new TestServerEventManager(host);
et.service.openClientFile("/a/a.ts");
et.assertProjectInfoTelemetryEvent({}, "/a/tsconfig.json");
const et = new TestServerEventManager([file, file2, tsconfig]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({}, tsconfig.path);
et.service.closeClientFile("/a/a.ts");
et.service.closeClientFile(file.path);
checkNumberOfProjects(et.service, { configuredProjects: 1 });
et.service.openClientFile("/b.ts");
et.service.openClientFile(file2.path);
checkNumberOfProjects(et.service, { inferredProjects: 1 });
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
et.service.openClientFile("/a/a.ts");
et.service.openClientFile(file.path);
checkNumberOfProjects(et.service, { configuredProjects: 1, inferredProjects: 1 });
et.hasZeroEvent(ts.server.ProjectInfoTelemetryEvent);
});
it("counts files by extension", () => {
const files = ["ts.ts", "tsx.tsx", "moo.ts", "dts.d.ts", "jsx.jsx", "js.js", "badExtension.badExtension"].map(f => makeFile(`/src/${f}`));
const notIncludedFile = makeFile("/bin/ts.js");
const compilerOptions: ts.CompilerOptions = { allowJs: true };
const host = new fakes.FakeServerHost({}, /*files*/ {
"/src/ts.ts": ``,
"/src/tsx.tsx": ``,
"/src/moo.ts": ``,
"/src/dts.d.ts": ``,
"/src/jsx.jsx": ``,
"/src/js.js": ``,
"/src/badExtension.badExtension": ``,
"/bin/ts.js": ``,
"/tsconfig.json": JSON.stringify({ compilerOptions, include: ["src"] }),
});
const tsconfig = makeFile("/tsconfig.json", { compilerOptions, include: ["src"] });
const et = new TestServerEventManager(host);
et.service.openClientFile("/src/ts.ts");
const et = new TestServerEventManager([...files, notIncludedFile, tsconfig]);
et.service.openClientFile(files[0].path);
et.assertProjectInfoTelemetryEvent({
fileStats: { ts: 2, tsx: 1, js: 1, jsx: 1, dts: 1 },
compilerOptions,
@ -62,11 +49,8 @@ namespace ts.projectSystem {
});
it("works with external project", () => {
const host = new fakes.FakeServerHost({}, /*files*/ {
"/a.ts": ``,
});
const et = new TestServerEventManager(host);
const file1 = makeFile("/a.ts");
const et = new TestServerEventManager([file1]);
const compilerOptions: ts.server.protocol.CompilerOptions = { strict: true };
const projectFileName = "/hunter2/foo.csproj";
@ -75,7 +59,7 @@ namespace ts.projectSystem {
// TODO: Apparently compilerOptions is mutated, so have to repeat it here!
et.assertProjectInfoTelemetryEvent({
projectId: core.sha1("/hunter2/foo.csproj"),
projectId: Harness.mockHash("/hunter2/foo.csproj"),
compilerOptions: { strict: true },
compileOnSave: true,
// These properties can't be present for an external project, so they are undefined instead of false.
@ -97,7 +81,7 @@ namespace ts.projectSystem {
function open(): void {
et.service.openExternalProject({
rootFiles: toExternalFiles(["/a.ts"]),
rootFiles: toExternalFiles([file1.path]),
options: compilerOptions,
projectFileName,
});
@ -106,6 +90,8 @@ namespace ts.projectSystem {
});
it("does not expose paths", () => {
const file = makeFile("/a.ts");
const compilerOptions: ts.CompilerOptions = {
project: "",
outFile: "hunter2.js",
@ -160,14 +146,10 @@ namespace ts.projectSystem {
lib: ["es6", "dom"],
};
(compilerOptions as any).unknownCompilerOption = "hunter2"; // These are always ignored.
const tsconfig = makeFile("/tsconfig.json", { compilerOptions, files: ["/a.ts"] });
const host = new fakes.FakeServerHost({}, /*files*/ {
"/a.ts": ``,
"/tsconfig.json": JSON.stringify({ compilerOptions, files: ["/a.ts"] }),
});
const et = new TestServerEventManager(host);
et.service.openClientFile("/a.ts");
const et = new TestServerEventManager([file, tsconfig]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({
compilerOptions: safeCompilerOptions,
@ -176,20 +158,18 @@ namespace ts.projectSystem {
});
it("sends telemetry for extends, files, include, exclude, and compileOnSave", () => {
const host = new fakes.FakeServerHost({}, /*files*/ {
"/hunter2/a.ts": ``,
"/tsconfig.json": JSON.stringify({
compilerOptions: {},
extends: "hunter2.json",
files: ["hunter2/a.ts"],
include: ["hunter2"],
exclude: ["hunter2"],
compileOnSave: true,
})
const file = makeFile("/hunter2/a.ts");
const tsconfig = makeFile("/tsconfig.json", {
compilerOptions: {},
extends: "hunter2.json",
files: ["hunter2/a.ts"],
include: ["hunter2"],
exclude: ["hunter2"],
compileOnSave: true,
});
const et = new TestServerEventManager(host);
et.service.openClientFile("/hunter2/a.ts");
const et = new TestServerEventManager([tsconfig, file]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({
extends: true,
files: true,
@ -209,22 +189,20 @@ namespace ts.projectSystem {
};
it("sends telemetry for typeAcquisition settings", () => {
const host = new fakes.FakeServerHost({}, /*files*/ {
"/a.js": ``,
"/jsconfig.json": JSON.stringify({
compilerOptions: {},
typeAcquisition: {
enable: true,
enableAutoDiscovery: false,
include: ["hunter2", "hunter3"],
exclude: [],
},
})
const file = makeFile("/a.js");
const jsconfig = makeFile("/jsconfig.json", {
compilerOptions: {},
typeAcquisition: {
enable: true,
enableAutoDiscovery: false,
include: ["hunter2", "hunter3"],
exclude: [],
},
});
const et = new TestServerEventManager(host);
et.service.openClientFile("/a.js");
const et = new TestServerEventManager([jsconfig, file]);
et.service.openClientFile(file.path);
et.assertProjectInfoTelemetryEvent({
projectId: core.sha1("/jsconfig.json"),
projectId: Harness.mockHash("/jsconfig.json"),
fileStats: fileStats({ js: 1 }),
compilerOptions: autoJsCompilerOptions,
typeAcquisition: {
@ -237,17 +215,14 @@ namespace ts.projectSystem {
});
it("detects whether language service was disabled", () => {
const host = new fakes.FakeServerHost({}, /*files*/ {
"/a.js": ``,
"/jsconfig.json": `{}`,
});
const et = new TestServerEventManager(host);
const file = makeFile("/a.js");
const tsconfig = makeFile("/jsconfig.json", {});
const et = new TestServerEventManager([tsconfig, file]);
et.host.getFileSize = () => server.maxProgramSizeForNonTsFiles + 1;
et.service.openClientFile("/a.js");
et.service.openClientFile(file.path);
et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent);
et.assertProjectInfoTelemetryEvent({
projectId: core.sha1("/jsconfig.json"),
projectId: Harness.mockHash("/jsconfig.json"),
fileStats: fileStats({ js: 1 }),
compilerOptions: autoJsCompilerOptions,
configFileName: "jsconfig.json",
@ -260,4 +235,8 @@ namespace ts.projectSystem {
});
});
});
function makeFile(path: string, content: {} = ""): projectSystem.FileOrFolder {
return { path, content: isString(content) ? "" : JSON.stringify(content) };
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -72,11 +72,17 @@ namespace utils {
let match: RegExpExecArray | null;
let lineStart = 0;
while (match = lineTerminatorRegExp.exec(text)) {
lines.push(text.slice(lineStart, match.index));
lineTerminators.push(match[0]);
if (lineStart !== match.index || lines.length > 0) {
lines.push(text.slice(lineStart, match.index));
lineTerminators.push(match[0]);
}
lineStart = match.index + match[0].length;
}
if (lineStart < text.length) {
lines.push(text.slice(lineStart));
}
const indentation = guessIndentation(lines);
let result = "";
@ -84,7 +90,9 @@ namespace utils {
const lineText = lines[i];
const line = indentation ? lineText.slice(indentation) : lineText;
result += line;
result += lineTerminators[i];
if (i < lineTerminators.length) {
result += lineTerminators[i];
}
}
return result;
}

View File

@ -17,6 +17,9 @@ namespace vfsutils {
* Posix-style path to the TypeScript compiler build outputs (including tsc.js, lib.d.ts, etc.)
*/
export const builtFolder = "/.ts";
export const tscPath = builtFolder + "/tsc.js";
export const libPath = builtFolder + "/lib.d.ts";
export const safelistPath = "/safelist.json";
/**
* Posix-style path to additional test libraries
@ -32,6 +35,9 @@ namespace vfsutils {
* DOS-style path to the TypeScript compiler build outputs (including tsc.js, lib.d.ts, etc.)
*/
export const dosBuiltFolder = "c:" + builtFolder;
export const dosTscPath = dosBuiltFolder + "/tsc.js";
export const dosLibPath = dosBuiltFolder + "/lib.d.ts";
export const dosSafelistPath = dosBuiltFolder + "/safelist.json";
/**
* DOS-style path to additional test libraries
@ -43,6 +49,29 @@ namespace vfsutils {
*/
export const dosSrcFolder = "c:" + srcFolder;
/** Default safelist.json content used by a number of tests. */
export const safelistContent = utils.dedent`
{
"commander": "commander",
"express": "express",
"jquery": "jquery",
"lodash": "lodash",
"moment": "moment",
"chroma": "chroma-js"
}`;
/** A minimal lib.d.ts used by a number of tests. */
export const emptyLibContent = utils.dedent`
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> {}`;
export function createResolver(io: Harness.IO): vfs.FileSystemResolver {
return {
readdirSync(path: string): string[] {