@sprucelabs/test-utils 3.1.83 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/AbstractSpruceTest.d.ts +10 -0
- package/build/AbstractSpruceTest.js +38 -0
- package/build/AssertionError.d.ts +3 -0
- package/build/AssertionError.js +15 -0
- package/build/StackCleaner.d.ts +4 -0
- package/build/StackCleaner.js +12 -0
- package/build/assert/assert.d.ts +44 -0
- package/build/assert/assert.js +227 -0
- package/build/assert/assert.utility.d.ts +28 -0
- package/build/assert/assert.utility.js +182 -0
- package/build/decorators.d.ts +8 -0
- package/build/decorators.js +74 -0
- package/build/esm/AbstractSpruceTest.d.ts +10 -0
- package/build/esm/AbstractSpruceTest.js +51 -0
- package/build/esm/AssertionError.d.ts +3 -0
- package/build/esm/AssertionError.js +9 -0
- package/build/esm/StackCleaner.d.ts +4 -0
- package/build/esm/StackCleaner.js +9 -0
- package/build/esm/assert/assert.d.ts +44 -0
- package/build/esm/assert/assert.js +233 -0
- package/build/esm/assert/assert.utility.d.ts +28 -0
- package/build/esm/assert/assert.utility.js +176 -0
- package/build/esm/decorators.d.ts +8 -0
- package/build/esm/decorators.js +80 -0
- package/build/esm/index.d.ts +6 -0
- package/build/esm/index.js +6 -0
- package/build/esm/utilities/errorAssert.js +2 -1
- package/build/index.d.ts +6 -0
- package/build/index.js +26 -1
- package/build/utilities/errorAssert.js +6 -5
- package/package.json +9 -3
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import path from 'path';
|
|
11
|
+
export default class AbstractSpruceTest {
|
|
12
|
+
static beforeAll() {
|
|
13
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
14
|
+
this.cwd = process.cwd();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
static afterAll() {
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () { });
|
|
19
|
+
}
|
|
20
|
+
static beforeEach() {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () { });
|
|
22
|
+
}
|
|
23
|
+
static afterEach() {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () { });
|
|
25
|
+
}
|
|
26
|
+
static resolvePath(...filePath) {
|
|
27
|
+
const cwd = this.cwd;
|
|
28
|
+
let builtPath = path.join(...filePath);
|
|
29
|
+
if (!cwd) {
|
|
30
|
+
throw new Error('You must call super.beforeAll().');
|
|
31
|
+
}
|
|
32
|
+
if (builtPath[0] !== '/') {
|
|
33
|
+
if (builtPath.substr(0, 2) === './') {
|
|
34
|
+
builtPath = builtPath.substr(1);
|
|
35
|
+
}
|
|
36
|
+
builtPath = path.join(cwd, builtPath);
|
|
37
|
+
}
|
|
38
|
+
return builtPath;
|
|
39
|
+
}
|
|
40
|
+
static wait(ms = 1000) {
|
|
41
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
setTimeout(() => resolve(true), ms);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
static log(...args) {
|
|
48
|
+
const str = args.map((a) => `${a}`).join(' ');
|
|
49
|
+
process.stderr.write(str);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import StackCleaner from './StackCleaner.js';
|
|
2
|
+
export default class AssertionError extends Error {
|
|
3
|
+
constructor(message, stack) {
|
|
4
|
+
var _a;
|
|
5
|
+
super(message);
|
|
6
|
+
this.message = StackCleaner.clean(message ? `${message}\n` : '');
|
|
7
|
+
this.stack = StackCleaner.clean(`${this.message}${((_a = stack !== null && stack !== void 0 ? stack : this.stack) !== null && _a !== void 0 ? _a : '').replace(message, '')}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default class StackCleaner {
|
|
2
|
+
static clean(stack) {
|
|
3
|
+
const lines = stack.split(/\r?\n/);
|
|
4
|
+
const filtered = lines.filter((line) => line.search(this.matchPattern) === -1);
|
|
5
|
+
const newStack = filtered.join('\n');
|
|
6
|
+
return newStack;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
StackCleaner.matchPattern = /spruce-test\/(?!src\/__tests__)|node_modules|internal\/process\/task_queues|@babel|regenerator-runtime\/runtime/gi;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { expectType } from 'ts-expect';
|
|
2
|
+
declare type RecursivePartial<T> = {
|
|
3
|
+
[P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object ? RecursivePartial<T[P]> : T[P];
|
|
4
|
+
};
|
|
5
|
+
declare type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
|
|
6
|
+
declare type IsAny<T> = IfAny<T, true, never>;
|
|
7
|
+
declare type TypeEqual<T, U> = IsAny<T> extends never ? Exclude<T, U> extends never ? Exclude<U, T> extends never ? true : false : false : false;
|
|
8
|
+
declare function isExactType<T, E, Pass = TypeEqual<T, E>>(_: Pass): void;
|
|
9
|
+
export interface ISpruceAssert {
|
|
10
|
+
isNumber(actual: any, message?: string): asserts actual is number;
|
|
11
|
+
isType: typeof expectType;
|
|
12
|
+
isExactType: typeof isExactType;
|
|
13
|
+
isArray<T extends any[]>(actual: any, message?: string): asserts actual is T;
|
|
14
|
+
areSameType<T>(actual: T, expected: T): asserts actual is T;
|
|
15
|
+
isEqual<T>(actual: T, expected: T, message?: string): asserts actual is T;
|
|
16
|
+
isNotEqual<T>(actual: T, expected: T, message?: string): asserts actual is T;
|
|
17
|
+
isEqualDeep<T>(actual: T, expected: T, message?: string, shouldAppendDelta?: boolean): asserts actual is T;
|
|
18
|
+
isAbove<T>(actual: T, floor: T, message?: string): void;
|
|
19
|
+
isBelow<T>(actual: T, ceiling: T, message?: string): void;
|
|
20
|
+
isUndefined<T>(actual: T, message?: string): void;
|
|
21
|
+
isTruthy<T = any>(value: T, message?: string): asserts value is NonNullable<T>;
|
|
22
|
+
isFalsy(value: any, message?: string): void;
|
|
23
|
+
isTrue(actual: boolean | undefined | null, message?: string): asserts actual is true;
|
|
24
|
+
isFalse(actual: boolean | undefined | null, message?: string): asserts actual is false;
|
|
25
|
+
isObject<T>(actual: T, message?: string): void;
|
|
26
|
+
isLength(actual: any[] | undefined | null, expected: number, message?: string): void;
|
|
27
|
+
isNull(actual: any, message?: string): void;
|
|
28
|
+
doesNotInclude<T>(haystack: T, needle: RecursivePartial<T>, message?: string): void;
|
|
29
|
+
doesNotInclude(haystack: string, needle: string, message?: string): void;
|
|
30
|
+
doesNotInclude(haystack: any, needle: string, message?: string): void;
|
|
31
|
+
doesNotInclude(haystack: any, needle: any, message?: string): void;
|
|
32
|
+
doesInclude<T>(haystack: T, needle: RecursivePartial<T>, message?: string): void;
|
|
33
|
+
doesInclude(haystack: string, needle: string, message?: string): void;
|
|
34
|
+
doesInclude(haystack: any, needle: string, message?: string): void;
|
|
35
|
+
doesInclude(haystack: any, needle: any, message?: string): void;
|
|
36
|
+
isString(actual: any, message?: string): asserts actual is string;
|
|
37
|
+
isFunction(actual: any, message?: string): asserts actual is Function;
|
|
38
|
+
hasAllFunctions(obj: any, functionNames: string[]): void;
|
|
39
|
+
doesThrow(cb: () => any, matcher?: string | RegExp | undefined, msg?: string | undefined): Error;
|
|
40
|
+
doesThrowAsync(cb: () => any | Promise<any>, matcher?: string | RegExp | undefined, msg?: string | undefined): Promise<Error>;
|
|
41
|
+
fail(message?: string): void;
|
|
42
|
+
}
|
|
43
|
+
declare const assert: ISpruceAssert;
|
|
44
|
+
export default assert;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import deepEqual from 'deep-equal';
|
|
12
|
+
import escapeRegExp from 'lodash/escapeRegExp.js';
|
|
13
|
+
import isObjectLike from 'lodash/isObjectLike.js';
|
|
14
|
+
import { expectType } from 'ts-expect';
|
|
15
|
+
import diff from 'variable-diff';
|
|
16
|
+
import assertUtil from './assert.utility.js';
|
|
17
|
+
const stringify = assertUtil.stringify.bind(assertUtil);
|
|
18
|
+
function isExactType(_) { }
|
|
19
|
+
const assert = {
|
|
20
|
+
areSameType() { },
|
|
21
|
+
isType: expectType,
|
|
22
|
+
isExactType,
|
|
23
|
+
isNumber(actual, message) {
|
|
24
|
+
if (typeof actual !== 'number') {
|
|
25
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not a number!`);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
isEqual(actual, expected, message) {
|
|
29
|
+
if (actual !== expected) {
|
|
30
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} does not equal ${stringify(expected)}`);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
isNotEqual(actual, expected, message) {
|
|
34
|
+
if (actual === expected) {
|
|
35
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} should not equal ${stringify(expected)}`);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
isEqualDeep(actual, expected, message, shouldAppendDelta = true) {
|
|
39
|
+
if (!deepEqual(actual, expected, { strict: true })) {
|
|
40
|
+
let result = diff(actual, expected);
|
|
41
|
+
this.fail(`${message !== null && message !== void 0 ? message : `Deep equal failed.\n\nActual would need the following changes to match expected:`}${shouldAppendDelta ? `\n\n${result.text}` : ``}`);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
isAbove(actual, floor, message) {
|
|
45
|
+
if (typeof actual !== 'number') {
|
|
46
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not a number!`);
|
|
47
|
+
}
|
|
48
|
+
if (actual <= floor) {
|
|
49
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not above ${stringify(floor)}`);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
isBelow(actual, ceiling, message) {
|
|
53
|
+
if (typeof actual !== 'number') {
|
|
54
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not a number!`);
|
|
55
|
+
}
|
|
56
|
+
if (actual >= ceiling) {
|
|
57
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not below ${stringify(ceiling)}`);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
isUndefined(actual, message) {
|
|
61
|
+
if (typeof actual !== 'undefined') {
|
|
62
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not undefined`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
isTruthy(actual, message) {
|
|
66
|
+
if (actual === false ||
|
|
67
|
+
actual === null ||
|
|
68
|
+
typeof actual === 'undefined' ||
|
|
69
|
+
actual === 0) {
|
|
70
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not truthy`);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
isFalsy(actual, message) {
|
|
74
|
+
if (actual) {
|
|
75
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not falsy`);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
isNull(actual, message) {
|
|
79
|
+
if (actual !== null) {
|
|
80
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not null`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
isString(actual, message) {
|
|
84
|
+
assertUtil.assertTypeof(actual, 'string', message);
|
|
85
|
+
},
|
|
86
|
+
isFunction(actual, message) {
|
|
87
|
+
assertUtil.assertTypeof(actual, 'function', message);
|
|
88
|
+
},
|
|
89
|
+
isTrue(actual, message) {
|
|
90
|
+
//@ts-ignore
|
|
91
|
+
this.isEqual(actual, true, message);
|
|
92
|
+
},
|
|
93
|
+
isFalse(actual, message) {
|
|
94
|
+
//@ts-ignore
|
|
95
|
+
this.isEqual(actual, false, message);
|
|
96
|
+
},
|
|
97
|
+
isObject(actual, message) {
|
|
98
|
+
if (!isObjectLike(actual)) {
|
|
99
|
+
throw this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not an object`);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
isArray(actual, message) {
|
|
103
|
+
if (!Array.isArray(actual)) {
|
|
104
|
+
throw this.fail(message !== null && message !== void 0 ? message : `${stringify(actual)} is not an array`);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
isLength(actual, expected, message) {
|
|
108
|
+
if (!actual) {
|
|
109
|
+
throw this.fail(message !== null && message !== void 0 ? message : `Expected array of length ${expected}, but got ${stringify(actual)}`);
|
|
110
|
+
}
|
|
111
|
+
//@ts-ignore
|
|
112
|
+
this.isEqual(actual.length, expected, message !== null && message !== void 0 ? message : `Expected length of ${stringify(expected)}, but got a length of ${stringify(actual.length)}`);
|
|
113
|
+
},
|
|
114
|
+
doesNotInclude(haystack, needle, message) {
|
|
115
|
+
let doesInclude = false;
|
|
116
|
+
try {
|
|
117
|
+
this.doesInclude(haystack, needle);
|
|
118
|
+
doesInclude = true;
|
|
119
|
+
}
|
|
120
|
+
catch (_a) {
|
|
121
|
+
doesInclude = false;
|
|
122
|
+
}
|
|
123
|
+
if (doesInclude) {
|
|
124
|
+
this.fail(message !== null && message !== void 0 ? message : `${stringify(haystack)} should not include ${stringify(needle)}, but it does`);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
doesInclude(haystack, needle, message) {
|
|
128
|
+
let msg = message !== null && message !== void 0 ? message : `Could not find ${stringify(needle)} in ${stringify(haystack)}`;
|
|
129
|
+
const isNeedleString = typeof needle === 'string';
|
|
130
|
+
const isNeedleRegex = needle instanceof RegExp;
|
|
131
|
+
if (typeof haystack === 'string' &&
|
|
132
|
+
(isNeedleString || isNeedleRegex) &&
|
|
133
|
+
haystack.search(isNeedleString ? escapeRegExp(needle) : needle) > -1) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const isHaystackObject = isObjectLike(haystack);
|
|
137
|
+
const { needleHasArrayNotation, path, expected } = assertUtil.parseIncludeNeedle(needle);
|
|
138
|
+
if (Array.isArray(haystack)) {
|
|
139
|
+
let cleanedNeedle = needle;
|
|
140
|
+
if (path && path.substr(0, 3) === '[].') {
|
|
141
|
+
cleanedNeedle = { [path.substr(3)]: expected };
|
|
142
|
+
}
|
|
143
|
+
const found = assertUtil.doHaystacksPassCheck(haystack, cleanedNeedle, this.doesInclude.bind(this));
|
|
144
|
+
if (found) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (isHaystackObject && isObjectLike(needle)) {
|
|
149
|
+
try {
|
|
150
|
+
//@ts-ignore
|
|
151
|
+
this.isEqualDeep(haystack, needle);
|
|
152
|
+
return;
|
|
153
|
+
// eslint-disable-next-line no-empty
|
|
154
|
+
}
|
|
155
|
+
catch (_a) { }
|
|
156
|
+
}
|
|
157
|
+
if (assertUtil.foundUsing3rdPartyIncludes(haystack, needle, isHaystackObject)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (!Array.isArray(haystack) &&
|
|
161
|
+
isHaystackObject &&
|
|
162
|
+
isObjectLike(needle) &&
|
|
163
|
+
Object.keys(needle).length === 1 &&
|
|
164
|
+
!needleHasArrayNotation &&
|
|
165
|
+
path) {
|
|
166
|
+
const actual = assertUtil.valueAtPath(haystack, path);
|
|
167
|
+
if (typeof actual === 'undefined') {
|
|
168
|
+
msg = `The path ${stringify(path)} was not found in ${stringify(haystack)}`;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
msg = `Expected ${chalk.green(stringify(needle[path]))} but found ${chalk.red(stringify(actual))} at ${stringify(path)} in ${stringify(haystack)}`;
|
|
172
|
+
}
|
|
173
|
+
if (typeof expected === 'string' &&
|
|
174
|
+
typeof actual === 'string' &&
|
|
175
|
+
actual.search(expected) > -1) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
else if (expected instanceof RegExp && expected.exec(actual)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
//@ts-ignore
|
|
183
|
+
this.isEqualDeep(expected, actual, msg, false);
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (isHaystackObject && isObjectLike(needle) && path) {
|
|
188
|
+
const { actualBeforeArray, pathAfterFirstArray } = assertUtil.splitPathBasedOnArrayNotation(path, haystack);
|
|
189
|
+
if (!Array.isArray(actualBeforeArray)) {
|
|
190
|
+
this.fail(msg);
|
|
191
|
+
}
|
|
192
|
+
const found = assertUtil.doHaystacksPassCheck(actualBeforeArray, {
|
|
193
|
+
[pathAfterFirstArray]: expected,
|
|
194
|
+
}, this.doesInclude.bind(this));
|
|
195
|
+
if (found) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
msg = `Could not find match ${stringify(expected)} at ${stringify(pathAfterFirstArray)} in ${stringify(actualBeforeArray)}.`;
|
|
199
|
+
}
|
|
200
|
+
this.fail(msg);
|
|
201
|
+
},
|
|
202
|
+
hasAllFunctions(obj, functionNames) {
|
|
203
|
+
functionNames.forEach((name) => {
|
|
204
|
+
if (typeof obj[name] !== 'function') {
|
|
205
|
+
this.fail(`A function named "${name}" does not exist on ${stringify(obj)}`);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
doesThrow(cb, matcher, msg) {
|
|
210
|
+
try {
|
|
211
|
+
cb();
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
assertUtil.checkDoesThrowError(matcher, err, msg);
|
|
215
|
+
return err;
|
|
216
|
+
}
|
|
217
|
+
this.fail('Expected a thrown error, but never got one!');
|
|
218
|
+
},
|
|
219
|
+
doesThrowAsync(cb, matcher, msg) {
|
|
220
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
221
|
+
try {
|
|
222
|
+
yield cb();
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
assertUtil.checkDoesThrowError(matcher, err, msg);
|
|
226
|
+
return err;
|
|
227
|
+
}
|
|
228
|
+
this.fail('Expected a thrown error, but never got one!');
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
fail: assertUtil.fail,
|
|
232
|
+
};
|
|
233
|
+
export default assert;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ISpruceAssert } from './assert';
|
|
2
|
+
export declare const UNDEFINED_PLACEHOLDER = "_____________undefined_____________";
|
|
3
|
+
export declare const FUNCTION_PLACEHOLDER = "_____________function_____________";
|
|
4
|
+
export declare const CIRCULAR_PLACEHOLDER = "_____________circular_____________";
|
|
5
|
+
export declare const NULL_PLACEHOLDER = "_____________null_____________";
|
|
6
|
+
declare const assertUtil: {
|
|
7
|
+
fail(message?: string, stack?: string): never;
|
|
8
|
+
stringify(object: any): string;
|
|
9
|
+
replacePlaceholders(str: string): string;
|
|
10
|
+
dropInPlaceholders(obj: Record<string, any>): Record<string, any>;
|
|
11
|
+
dropInPlaceholder(obj: Record<string, any>, checker: (obj: any, depth: number) => boolean, placeholder: string, depth?: number): Record<string, any>;
|
|
12
|
+
doHaystacksPassCheck(haystacks: any[], needle: any, check: ISpruceAssert['doesInclude']): boolean;
|
|
13
|
+
assertTypeof(actual: any, type: string, message: string | undefined): void;
|
|
14
|
+
checkDoesThrowError(matcher: string | RegExp | undefined, err: Error, msg?: string | undefined): void;
|
|
15
|
+
partialContains(object: any, subObject: any): boolean | undefined;
|
|
16
|
+
valueAtPath(object: Record<string, any>, path: string): any;
|
|
17
|
+
parseIncludeNeedle(needle: any): {
|
|
18
|
+
needleHasArrayNotation: boolean;
|
|
19
|
+
path?: string | undefined;
|
|
20
|
+
expected?: any;
|
|
21
|
+
};
|
|
22
|
+
splitPathBasedOnArrayNotation(path: string, haystack: any): {
|
|
23
|
+
actualBeforeArray: any;
|
|
24
|
+
pathAfterFirstArray: string;
|
|
25
|
+
};
|
|
26
|
+
foundUsing3rdPartyIncludes(haystack: any, needle: any, isHaystackObject: boolean): boolean;
|
|
27
|
+
};
|
|
28
|
+
export default assertUtil;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import get from 'lodash/get.js';
|
|
3
|
+
import includes from 'lodash/includes.js';
|
|
4
|
+
import isObject from 'lodash/isObject.js';
|
|
5
|
+
import isObjectLike from 'lodash/isObjectLike.js';
|
|
6
|
+
import AssertionError from '../AssertionError.js';
|
|
7
|
+
export const UNDEFINED_PLACEHOLDER = '_____________undefined_____________';
|
|
8
|
+
export const FUNCTION_PLACEHOLDER = '_____________function_____________';
|
|
9
|
+
export const CIRCULAR_PLACEHOLDER = '_____________circular_____________';
|
|
10
|
+
export const NULL_PLACEHOLDER = '_____________null_____________';
|
|
11
|
+
const assertUtil = {
|
|
12
|
+
fail(message, stack) {
|
|
13
|
+
throw new AssertionError(message !== null && message !== void 0 ? message : 'Fail!', stack);
|
|
14
|
+
},
|
|
15
|
+
stringify(object) {
|
|
16
|
+
var _a;
|
|
17
|
+
let stringified;
|
|
18
|
+
if (Array.isArray(object)) {
|
|
19
|
+
stringified = `[\n${object.map((o) => this.stringify(o).split('\n').join('\n\t'))}\n]`;
|
|
20
|
+
}
|
|
21
|
+
else if (typeof object === 'number') {
|
|
22
|
+
// this hack allows the Spruce Test Reporter to render number errors (they got eaten by terminal-kit's style regex)
|
|
23
|
+
stringified = chalk.bgBlack.white(` ${object} `);
|
|
24
|
+
}
|
|
25
|
+
else if (object instanceof Error) {
|
|
26
|
+
stringified = `${(_a = object.stack) !== null && _a !== void 0 ? _a : object.message}`;
|
|
27
|
+
}
|
|
28
|
+
else if (object instanceof RegExp) {
|
|
29
|
+
stringified = `${object.toString()}`;
|
|
30
|
+
}
|
|
31
|
+
else if (typeof object === 'undefined') {
|
|
32
|
+
stringified = 'undefined';
|
|
33
|
+
}
|
|
34
|
+
else if (typeof object === 'string') {
|
|
35
|
+
stringified = `"${object}"`;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
stringified = JSON.stringify(assertUtil.dropInPlaceholders(object), undefined, 2).replace(/\\/g, '');
|
|
39
|
+
}
|
|
40
|
+
if (stringified.length > 5000) {
|
|
41
|
+
stringified =
|
|
42
|
+
stringified.substr(0, 1000) +
|
|
43
|
+
'\n\n... big object ...\n\n' +
|
|
44
|
+
stringified.substr(stringified.length - 1000);
|
|
45
|
+
}
|
|
46
|
+
stringified = assertUtil.replacePlaceholders(stringified);
|
|
47
|
+
return `\n\n${chalk.bold(stringified)}\n\n`;
|
|
48
|
+
},
|
|
49
|
+
replacePlaceholders(str) {
|
|
50
|
+
return str
|
|
51
|
+
.replace(new RegExp(`"${UNDEFINED_PLACEHOLDER}"`, 'g'), chalk.italic('undefined'))
|
|
52
|
+
.replace(new RegExp(`"${FUNCTION_PLACEHOLDER}"`, 'g'), chalk.italic('Function'))
|
|
53
|
+
.replace(new RegExp(`"${NULL_PLACEHOLDER}"`, 'g'), chalk.italic('NULL'));
|
|
54
|
+
},
|
|
55
|
+
dropInPlaceholders(obj) {
|
|
56
|
+
const checkedObjects = [{ obj, depth: 0 }];
|
|
57
|
+
let updated = this.dropInPlaceholder(obj, (obj, depth) => {
|
|
58
|
+
if (isObject(obj) &&
|
|
59
|
+
checkedObjects.some((checked) => {
|
|
60
|
+
return checked.obj === obj && checked.depth < depth;
|
|
61
|
+
})) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
checkedObjects.push({ obj, depth });
|
|
65
|
+
return false;
|
|
66
|
+
}, CIRCULAR_PLACEHOLDER);
|
|
67
|
+
updated = this.dropInPlaceholder(updated, (obj) => typeof obj === 'undefined', UNDEFINED_PLACEHOLDER);
|
|
68
|
+
updated = this.dropInPlaceholder(updated, (obj) => typeof obj === 'function', FUNCTION_PLACEHOLDER);
|
|
69
|
+
updated = this.dropInPlaceholder(updated, (obj) => obj === null, NULL_PLACEHOLDER);
|
|
70
|
+
return updated;
|
|
71
|
+
},
|
|
72
|
+
dropInPlaceholder(obj, checker, placeholder, depth = 1) {
|
|
73
|
+
if (!isObject(obj)) {
|
|
74
|
+
return obj;
|
|
75
|
+
}
|
|
76
|
+
const updated = Array.isArray(obj) ? [] : {};
|
|
77
|
+
Object.keys(obj).forEach((key) => {
|
|
78
|
+
//@ts-ignore
|
|
79
|
+
updated[key] =
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
checker(obj[key], depth) ? placeholder : obj[key];
|
|
82
|
+
//@ts-ignore
|
|
83
|
+
if (typeof updated[key] !== 'function' && isObject(updated[key])) {
|
|
84
|
+
//@ts-ignore
|
|
85
|
+
updated[key] = this.dropInPlaceholder(
|
|
86
|
+
//@ts-ignore
|
|
87
|
+
updated[key], checker, placeholder, depth + 1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return updated;
|
|
91
|
+
},
|
|
92
|
+
doHaystacksPassCheck(haystacks, needle, check) {
|
|
93
|
+
return !!haystacks.find((haystack) => {
|
|
94
|
+
try {
|
|
95
|
+
check(haystack, needle);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (_a) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
assertTypeof(actual, type, message) {
|
|
104
|
+
if (typeof actual !== type) {
|
|
105
|
+
this.fail(message !== null && message !== void 0 ? message : `${JSON.stringify(actual)} is not a ${type}`);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
checkDoesThrowError(matcher, err, msg) {
|
|
109
|
+
var _a, _b;
|
|
110
|
+
const message = (_b = (_a = err.stack) !== null && _a !== void 0 ? _a : err.message) !== null && _b !== void 0 ? _b : '**MISSING ERROR MESSAGE**';
|
|
111
|
+
if (typeof matcher === 'string' && message.search(matcher) === -1) {
|
|
112
|
+
this.fail(msg !== null && msg !== void 0 ? msg : `Expected thrown error whose message contains: \n\n${chalk.bold(matcher)}\n\nbut got back:\n\n\`${chalk.bold(message)}\`.`, '\n\nStack: ' + err.stack);
|
|
113
|
+
}
|
|
114
|
+
else if (matcher instanceof RegExp && message.search(matcher) === -1) {
|
|
115
|
+
this.fail(msg !== null && msg !== void 0 ? msg : `Expected thrown error whose message matches the regex: \n\n${chalk.bold(matcher)}\n\nbut got back:\n\n\`${chalk.bold(message)}\`.`, '\n\nStack: ' + err.stack);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
partialContains(object, subObject) {
|
|
119
|
+
const objProps = object ? Object.getOwnPropertyNames(object) : [];
|
|
120
|
+
const subProps = subObject ? Object.getOwnPropertyNames(subObject) : [];
|
|
121
|
+
if (objProps.length == 0 || subProps.length === 0) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (subProps.length > objProps.length) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
for (const subProp of subProps) {
|
|
128
|
+
if (!Object.prototype.hasOwnProperty.call(object, subProp)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if ((!isObjectLike(object[subProp]) || !isObjectLike(subObject[subProp])) &&
|
|
132
|
+
object[subProp] !== subObject[subProp]) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
if (isObjectLike(object[subProp]) &&
|
|
136
|
+
isObjectLike(subObject[subProp]) &&
|
|
137
|
+
!this.partialContains(object[subProp], subObject[subProp])) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
},
|
|
143
|
+
valueAtPath(object, path) {
|
|
144
|
+
return get(object, path);
|
|
145
|
+
},
|
|
146
|
+
parseIncludeNeedle(needle) {
|
|
147
|
+
const path = Object.keys(needle)[0];
|
|
148
|
+
const expected = path && needle[path];
|
|
149
|
+
const needleHasArrayNotation = !!(path && path.search(/\[\]\./) > -1);
|
|
150
|
+
return { needleHasArrayNotation, path, expected };
|
|
151
|
+
},
|
|
152
|
+
splitPathBasedOnArrayNotation(path, haystack) {
|
|
153
|
+
var _a;
|
|
154
|
+
const pathParts = path.split('[].');
|
|
155
|
+
const pathToFirstArray = (_a = pathParts.shift()) !== null && _a !== void 0 ? _a : '';
|
|
156
|
+
const pathAfterFirstArray = pathParts.join('[].');
|
|
157
|
+
const actualBeforeArray = this.valueAtPath(haystack, pathToFirstArray);
|
|
158
|
+
return { actualBeforeArray, pathAfterFirstArray };
|
|
159
|
+
},
|
|
160
|
+
foundUsing3rdPartyIncludes(haystack, needle, isHaystackObject) {
|
|
161
|
+
let passed = false;
|
|
162
|
+
if (typeof haystack === 'string' &&
|
|
163
|
+
typeof needle === 'string' &&
|
|
164
|
+
haystack.search(needle) > -1) {
|
|
165
|
+
passed = true;
|
|
166
|
+
}
|
|
167
|
+
if (isHaystackObject && includes(haystack, needle)) {
|
|
168
|
+
passed = true;
|
|
169
|
+
}
|
|
170
|
+
if (isHaystackObject && this.partialContains(haystack, needle)) {
|
|
171
|
+
passed = true;
|
|
172
|
+
}
|
|
173
|
+
return passed;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
export default assertUtil;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Test decorator */
|
|
2
|
+
declare function test(description?: string, ...args: any[]): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
3
|
+
declare namespace test {
|
|
4
|
+
var only: (description?: string | undefined, ...args: any[]) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
5
|
+
var todo: (description?: string | undefined, ..._args: any[]) => (target: any, propertyKey: string) => void;
|
|
6
|
+
var skip: (description?: string | undefined, ...args: any[]) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
7
|
+
}
|
|
8
|
+
export default test;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
/** Hooks up before, after, etc. */
|
|
11
|
+
function hookupTestClass(target) {
|
|
12
|
+
if (target.__isTestingHookedUp) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
target.__isTestingHookedUp = true;
|
|
16
|
+
const hooks = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'];
|
|
17
|
+
hooks.forEach((hook) => {
|
|
18
|
+
// Have they defined a hook
|
|
19
|
+
if (!target[hook]) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
if (global[hook]) {
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
global[hook](() => __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
return target[hook]();
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Test decorator */
|
|
32
|
+
export default function test(description, ...args) {
|
|
33
|
+
return function (target, propertyKey, descriptor) {
|
|
34
|
+
// Lets attach before/after
|
|
35
|
+
hookupTestClass(target);
|
|
36
|
+
const bound = descriptor.value.bind(target);
|
|
37
|
+
// Make sure each test gets the spruce
|
|
38
|
+
it(description !== null && description !== void 0 ? description : propertyKey, () => __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
//@ts-ignore
|
|
40
|
+
global.activeTest = {
|
|
41
|
+
file: target.name,
|
|
42
|
+
test: propertyKey,
|
|
43
|
+
};
|
|
44
|
+
return bound(...args);
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Only decorator */
|
|
49
|
+
test.only = (description, ...args) => {
|
|
50
|
+
return function (target, propertyKey, descriptor) {
|
|
51
|
+
// Lets attach before/after
|
|
52
|
+
hookupTestClass(target);
|
|
53
|
+
const bound = descriptor.value.bind(target);
|
|
54
|
+
// Make sure each test gets the spruce
|
|
55
|
+
it.only(description !== null && description !== void 0 ? description : propertyKey, () => __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
return bound(...args);
|
|
57
|
+
}));
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/** Todo decorator */
|
|
61
|
+
test.todo = (description, ..._args) => {
|
|
62
|
+
return function (target, propertyKey) {
|
|
63
|
+
// Lets attach before/after
|
|
64
|
+
hookupTestClass(target);
|
|
65
|
+
// Make sure each test gets the spruce
|
|
66
|
+
it.todo(description !== null && description !== void 0 ? description : propertyKey);
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/** Skip decorator */
|
|
70
|
+
test.skip = (description, ...args) => {
|
|
71
|
+
return function (target, propertyKey, descriptor) {
|
|
72
|
+
// Lets attach before/after
|
|
73
|
+
hookupTestClass(target);
|
|
74
|
+
const bound = descriptor.value.bind(target);
|
|
75
|
+
// Make sure each test gets the spruce
|
|
76
|
+
it.skip(description !== null && description !== void 0 ? description : propertyKey, () => __awaiter(this, void 0, void 0, function* () {
|
|
77
|
+
return bound(...args);
|
|
78
|
+
}));
|
|
79
|
+
};
|
|
80
|
+
};
|
package/build/esm/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export { default as errorAssert } from './utilities/errorAssert';
|
|
2
2
|
export { default as errorAssertUtil } from './utilities/errorAssert.utility';
|
|
3
3
|
export { default as generateId } from './utilities/generateId.utility';
|
|
4
|
+
export { default } from './AbstractSpruceTest';
|
|
5
|
+
export { default as assert } from './assert/assert';
|
|
6
|
+
export * from './assert/assert';
|
|
7
|
+
export { default as test } from './decorators';
|
|
8
|
+
export { default as assertUtil } from './assert/assert.utility';
|
|
9
|
+
export { default as StackCleaner } from './StackCleaner';
|