mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-17 11:24:29 -05:00
Revert changes to unit tests
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user