mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 03:23:08 -06:00
Revert changes to unit tests
This commit is contained in:
parent
0b963489a7
commit
f153b04f35
12
Gulpfile.ts
12
Gulpfile.ts
@ -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"]);
|
||||
});
|
||||
14
Jakefile.js
14
Jakefile.js
@ -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"`;
|
||||
|
||||
@ -85,7 +85,6 @@
|
||||
"travis-fold": "latest",
|
||||
"ts-node": "latest",
|
||||
"tslint": "latest",
|
||||
"typemock": "file:scripts/typemock",
|
||||
"typescript": "next",
|
||||
"vinyl": "latest",
|
||||
"xml2js": "^0.4.19"
|
||||
|
||||
@ -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"]);
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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">`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,5 +0,0 @@
|
||||
import "./argTests";
|
||||
import "./timesTests";
|
||||
import "./mockTests";
|
||||
import "./injectTests";
|
||||
import "./timersTests";
|
||||
@ -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());
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,3 +0,0 @@
|
||||
import { install } from "source-map-support";
|
||||
|
||||
install();
|
||||
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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}>`;
|
||||
}
|
||||
}
|
||||
@ -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/**/*"
|
||||
]
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)", () => {
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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
@ -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;
|
||||
}
|
||||
|
||||
@ -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[] {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user