tape-six 0.9.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/src/Tester.js ADDED
@@ -0,0 +1,411 @@
1
+ import {equal, match, any} from './deep6/index.js';
2
+
3
+ const throwHelper = fn => {
4
+ try {
5
+ fn();
6
+ } catch (error) {
7
+ return error;
8
+ }
9
+ return null;
10
+ };
11
+
12
+ class Tester {
13
+ constructor(state, testNumber) {
14
+ this.state = state;
15
+ this.testNumber = testNumber;
16
+ }
17
+
18
+ plan(n) {
19
+ this.state.setPlan(n);
20
+ }
21
+
22
+ comment(msg) {
23
+ this.state.emit({type: 'comment', name: msg || 'comment', test: this.testNumber, marker: new Error(), time: this.state.timer.now()});
24
+ }
25
+
26
+ skipTest(...args) {
27
+ let msg;
28
+ for (let i = args.length - 1; i >= 0; --i) {
29
+ if (typeof args[i] == 'string') {
30
+ msg = args[i];
31
+ break;
32
+ }
33
+ }
34
+ this.state.emit({
35
+ name: msg || 'skipped test',
36
+ test: this.testNumber,
37
+ marker: new Error(),
38
+ time: this.state.timer.now(),
39
+ skip: true,
40
+ operator: 'skip'
41
+ });
42
+ }
43
+
44
+ bailOut(msg) {
45
+ this.state.emit({
46
+ type: 'bail-out',
47
+ name: msg || 'bail out',
48
+ test: this.testNumber,
49
+ marker: new Error(),
50
+ time: this.state.timer.now(),
51
+ operator: 'bailOut'
52
+ });
53
+ }
54
+
55
+ // asserts
56
+
57
+ pass(msg) {
58
+ this.state.emit({
59
+ name: msg || 'pass',
60
+ test: this.testNumber,
61
+ marker: new Error(),
62
+ time: this.state.timer.now(),
63
+ operator: 'pass',
64
+ data: {expected: true, actual: true}
65
+ });
66
+ }
67
+
68
+ fail(msg) {
69
+ this.state.emit({
70
+ name: msg || 'fail',
71
+ test: this.testNumber,
72
+ marker: new Error(),
73
+ time: this.state.timer.now(),
74
+ operator: 'fail',
75
+ fail: true,
76
+ data: {expected: true, actual: false}
77
+ });
78
+ }
79
+
80
+ ok(value, msg) {
81
+ this.state.emit({
82
+ name: msg || 'should be truthy',
83
+ test: this.testNumber,
84
+ marker: new Error(),
85
+ time: this.state.timer.now(),
86
+ operator: 'ok',
87
+ fail: !value,
88
+ data: {
89
+ expected: true,
90
+ actual: value
91
+ }
92
+ });
93
+ }
94
+
95
+ notOk(value, msg) {
96
+ this.state.emit({
97
+ name: msg || 'should be falsy',
98
+ test: this.testNumber,
99
+ marker: new Error(),
100
+ time: this.state.timer.now(),
101
+ operator: 'notOk',
102
+ fail: !!value,
103
+ data: {
104
+ expected: false,
105
+ actual: value
106
+ }
107
+ });
108
+ }
109
+
110
+ error(error, msg) {
111
+ this.state.emit({
112
+ name: msg || String(error),
113
+ test: this.testNumber,
114
+ marker: new Error(),
115
+ time: this.state.timer.now(),
116
+ operator: 'error',
117
+ fail: !!error,
118
+ data: {
119
+ expected: true,
120
+ actual: error
121
+ }
122
+ });
123
+ }
124
+
125
+ strictEqual(a, b, msg) {
126
+ this.state.emit({
127
+ name: msg || 'should be strictly equal',
128
+ test: this.testNumber,
129
+ marker: new Error(),
130
+ time: this.state.timer.now(),
131
+ operator: 'equal',
132
+ fail: typeof a == 'object' ? a !== b : !equal(a, b),
133
+ data: {
134
+ expected: b,
135
+ actual: a
136
+ }
137
+ });
138
+ }
139
+
140
+ notStrictEqual(a, b, msg) {
141
+ this.state.emit({
142
+ name: msg || 'should not be strictly equal',
143
+ test: this.testNumber,
144
+ marker: new Error(),
145
+ time: this.state.timer.now(),
146
+ operator: 'notEqual',
147
+ fail: typeof a == 'object' ? a === b : equal(a, b),
148
+ data: {
149
+ expected: b,
150
+ actual: a
151
+ }
152
+ });
153
+ }
154
+
155
+ looseEqual(a, b, msg) {
156
+ this.state.emit({
157
+ name: msg || 'should be loosely equal',
158
+ test: this.testNumber,
159
+ marker: new Error(),
160
+ time: this.state.timer.now(),
161
+ operator: 'looseEqual',
162
+ fail: a != b,
163
+ data: {
164
+ expected: b,
165
+ actual: a
166
+ }
167
+ });
168
+ }
169
+
170
+ notLooseEqual(a, b, msg) {
171
+ this.state.emit({
172
+ name: msg || 'should not be loosely equal',
173
+ test: this.testNumber,
174
+ marker: new Error(),
175
+ time: this.state.timer.now(),
176
+ operator: 'notLooseEqual',
177
+ fail: a == b,
178
+ data: {
179
+ expected: b,
180
+ actual: a
181
+ }
182
+ });
183
+ }
184
+
185
+ deepEqual(a, b, msg) {
186
+ this.state.emit({
187
+ name: msg || 'should be deeply equivalent',
188
+ test: this.testNumber,
189
+ marker: new Error(),
190
+ time: this.state.timer.now(),
191
+ operator: 'deepEqual',
192
+ fail: !equal(a, b),
193
+ data: {
194
+ expected: b,
195
+ actual: a
196
+ }
197
+ });
198
+ }
199
+
200
+ notDeepEqual(a, b, msg) {
201
+ this.state.emit({
202
+ name: msg || 'should not be deeply equivalent',
203
+ test: this.testNumber,
204
+ marker: new Error(),
205
+ time: this.state.timer.now(),
206
+ operator: 'notDeepEqual',
207
+ fail: equal(a, b),
208
+ data: {
209
+ expected: b,
210
+ actual: a
211
+ }
212
+ });
213
+ }
214
+
215
+ deepLooseEqual(a, b, msg) {
216
+ this.state.emit({
217
+ name: msg || 'should be loosely equal',
218
+ test: this.testNumber,
219
+ marker: new Error(),
220
+ time: this.state.timer.now(),
221
+ operator: 'deepLooseEqual',
222
+ fail: !equal(a, b, {circular: true, loose: true}),
223
+ data: {
224
+ expected: b,
225
+ actual: a
226
+ }
227
+ });
228
+ }
229
+
230
+ notDeepLooseEqual(a, b, msg) {
231
+ this.state.emit({
232
+ name: msg || 'should not be loosely equal',
233
+ test: this.testNumber,
234
+ marker: new Error(),
235
+ time: this.state.timer.now(),
236
+ operator: 'notDeepLooseEqual',
237
+ fail: equal(a, b, {circular: true, loose: true}),
238
+ data: {
239
+ expected: b,
240
+ actual: a
241
+ }
242
+ });
243
+ }
244
+
245
+ throws(fn, msg) {
246
+ if (typeof fn != 'function') throw new TypeError('the first argument should be a function');
247
+ const result = throwHelper(fn);
248
+ this.state.emit({
249
+ name: msg || 'should throw',
250
+ test: this.testNumber,
251
+ marker: new Error(),
252
+ time: this.state.timer.now(),
253
+ operator: 'throws',
254
+ fail: !result,
255
+ data: {
256
+ expected: null,
257
+ actual: result
258
+ }
259
+ });
260
+ }
261
+
262
+ doesNotThrow(fn, msg) {
263
+ if (typeof fn != 'function') throw new TypeError('the first argument should be a function');
264
+ const result = throwHelper(fn);
265
+ this.state.emit({
266
+ name: msg || 'should not throw',
267
+ test: this.testNumber,
268
+ marker: new Error(),
269
+ time: this.state.timer.now(),
270
+ operator: 'doesNotThrow',
271
+ fail: !!result,
272
+ data: {
273
+ expected: null,
274
+ actual: result
275
+ }
276
+ });
277
+ }
278
+
279
+ matchString(string, regexp, msg) {
280
+ if (typeof string != 'string') throw new TypeError('the first argument should be a string');
281
+ if (!regexp || typeof regexp != 'object' || typeof regexp.test != 'function')
282
+ throw new TypeError('the second argument should be a regular expression object');
283
+ this.state.emit({
284
+ name: msg || 'should match regular expression',
285
+ test: this.testNumber,
286
+ marker: new Error(),
287
+ time: this.state.timer.now(),
288
+ operator: 'matchString',
289
+ fail: !regexp.test(string),
290
+ data: {
291
+ expected: regexp,
292
+ actual: string
293
+ }
294
+ });
295
+ }
296
+
297
+ doesNotMatchString(string, regexp, msg) {
298
+ if (typeof string != 'string') throw new TypeError('the first argument should be a string');
299
+ if (!regexp || typeof regexp != 'object' || typeof regexp.test != 'function')
300
+ throw new TypeError('the second argument should be a regular expression object');
301
+ this.state.emit({
302
+ name: msg || 'should not match regular expression',
303
+ test: this.testNumber,
304
+ marker: new Error(),
305
+ time: this.state.timer.now(),
306
+ operator: 'doesNotMatch',
307
+ fail: regexp.test(string),
308
+ data: {
309
+ expected: regexp,
310
+ actual: string
311
+ }
312
+ });
313
+ }
314
+
315
+ match(a, b, msg) {
316
+ this.state.emit({
317
+ name: msg || 'should match object',
318
+ test: this.testNumber,
319
+ marker: new Error(),
320
+ time: this.state.timer.now(),
321
+ operator: 'match',
322
+ fail: !match(a, b),
323
+ data: {
324
+ expected: b,
325
+ actual: a
326
+ }
327
+ });
328
+ }
329
+
330
+ doesNotMatch(a, b, msg) {
331
+ this.state.emit({
332
+ name: msg || 'should not match object',
333
+ test: this.testNumber,
334
+ marker: new Error(),
335
+ time: this.state.timer.now(),
336
+ operator: 'doesNotMatchObject',
337
+ fail: match(a, b),
338
+ data: {
339
+ expected: b,
340
+ actual: a
341
+ }
342
+ });
343
+ }
344
+
345
+ rejects(promise, msg) {
346
+ if (!promise || typeof promise.then != 'function') throw new TypeError('the first argument should be a promise');
347
+ return promise
348
+ .then(
349
+ () => null,
350
+ error => error
351
+ )
352
+ .then(result => {
353
+ this.state.emit({
354
+ name: msg || 'should be rejected',
355
+ test: this.testNumber,
356
+ marker: new Error(),
357
+ time: this.state.timer.now(),
358
+ operator: 'rejects',
359
+ fail: !result,
360
+ data: {
361
+ expected: null,
362
+ actual: result
363
+ }
364
+ });
365
+ });
366
+ }
367
+
368
+ resolves(promise, msg) {
369
+ if (!promise || typeof promise.then != 'function') throw new TypeError('the first argument should be a promise');
370
+ return promise
371
+ .then(
372
+ () => null,
373
+ error => error
374
+ )
375
+ .then(result => {
376
+ this.state.emit({
377
+ name: msg || 'should not be rejected',
378
+ test: this.testNumber,
379
+ marker: new Error(),
380
+ time: this.state.timer.now(),
381
+ operator: 'resolves',
382
+ fail: !!result,
383
+ data: {
384
+ expected: null,
385
+ actual: result
386
+ }
387
+ });
388
+ });
389
+ }
390
+
391
+ // missing: T (eval)
392
+ }
393
+ Tester.prototype.any = Tester.prototype._ = any;
394
+
395
+ const setAliases = (source, aliases) => aliases.split(', ').forEach(alias => (Tester.prototype[alias] = Tester.prototype[source]));
396
+
397
+ setAliases('ok', 'true, assert');
398
+ setAliases('notOk', 'false, notok');
399
+ setAliases('error', 'ifError, ifErr, iferror');
400
+ setAliases('strictEqual', 'is, equal, equals, isEqual, strictEquals');
401
+ setAliases('notStrictEqual', 'not, notEqual, notEquals, isNotEqual, doesNotEqual, isInequal, notStrictEquals, isNot');
402
+ setAliases('looseEqual', 'looseEquals');
403
+ setAliases('notLooseEqual', 'notLooseEquals');
404
+ setAliases('deepEqual', 'same, deepEquals, isEquivalent');
405
+ setAliases('notDeepEqual', 'notSame, notDeepEquals, notEquivalent, notDeeply, isNotDeepEqual, isNotDeepEqual, isNotEquivalent, isInequivalent');
406
+ setAliases('rejects', 'doesNotResolve');
407
+ setAliases('resolves', 'doesNotReject');
408
+
409
+ // test() (an embedded test runner) is added in ./test.js to avoid circular dependencies
410
+
411
+ export default Tester;
@@ -0,0 +1,174 @@
1
+ // Env
2
+
3
+ const keyDepth = Symbol.for('deep6.env.depth');
4
+
5
+ const collectSymbols = object => {
6
+ const symbols = new Set();
7
+ while (object && typeof object == 'object') {
8
+ Object.getOwnPropertySymbols(object).forEach(symbol => symbols.add(symbol));
9
+ object = Object.getPrototypeOf(object);
10
+ }
11
+ symbols.delete(keyDepth);
12
+ return Array.from(symbols);
13
+ };
14
+
15
+ const ensure = (object, depth) => {
16
+ while (object[keyDepth] > depth) object = object.getPrototypeOf(object);
17
+ if (object[keyDepth] < depth) {
18
+ object = Object.create(object);
19
+ object[keyDepth] = depth;
20
+ }
21
+ return object;
22
+ };
23
+
24
+ class Env {
25
+ constructor() {
26
+ this.variables = Object.create(null);
27
+ this.values = Object.create(null);
28
+ this.depth = this.variables[keyDepth] = this.values[keyDepth] = 0;
29
+ }
30
+ push() {
31
+ ++this.depth;
32
+ }
33
+ pop() {
34
+ if (this.depth < 1) throw new Error('attempt to pop a frame with an empty stack');
35
+ --this.depth;
36
+ while (this.variables[keyDepth] > this.depth) this.variables = Object.getPrototypeOf(this.variables);
37
+ while (this.values[keyDepth] > this.depth) this.values = Object.getPrototypeOf(this.values);
38
+ }
39
+ revert(depth) {
40
+ if (this.depth < depth) throw new Error('attempt to revert a stack to a higher depth');
41
+ while (this.variables[keyDepth] > depth) this.variables = Object.getPrototypeOf(this.variables);
42
+ while (this.values[keyDepth] > depth) this.values = Object.getPrototypeOf(this.values);
43
+ this.depth = depth;
44
+ }
45
+ bindVar(name1, name2) {
46
+ const depth = this.depth,
47
+ vars = (this.variables = ensure(this.variables, depth));
48
+ let u1 = vars[name1],
49
+ u2 = vars[name2];
50
+ u1 && (u1 = vars[name1] = ensure(u1, depth));
51
+ u2 && (u2 = vars[name2] = ensure(u2, depth));
52
+ if (u1) {
53
+ if (u2) {
54
+ for (const k in u2) {
55
+ vars[k] = u1;
56
+ u1[k] = 1;
57
+ }
58
+ collectSymbols(u2).forEach(k => ((vars[k] = u1), (u1[k] = 1)));
59
+ } else {
60
+ vars[name2] = u1;
61
+ u1[name2] = 1;
62
+ }
63
+ } else {
64
+ if (u2) {
65
+ vars[name1] = u2;
66
+ u2[name1] = 1;
67
+ } else {
68
+ u2 = Object.create(null);
69
+ u2[keyDepth] = depth;
70
+ vars[name1] = vars[name2] = u2;
71
+ u2[name1] = u2[name2] = 1;
72
+ }
73
+ }
74
+ }
75
+ bindVal(name, val) {
76
+ const depth = this.depth,
77
+ values = (this.values = ensure(this.values, depth)),
78
+ vars = (this.variables = ensure(this.variables, depth));
79
+ let u = vars[name];
80
+ u && (u = vars[name] = ensure(u, depth));
81
+ if (u) {
82
+ for (const k in u) {
83
+ values[k] = val;
84
+ vars[k] = null;
85
+ }
86
+ collectSymbols(u).forEach(k => ((values[k] = val), (vars[k] = null)));
87
+ } else {
88
+ values[name] = val;
89
+ }
90
+ }
91
+ // helpers
92
+ isBound(name) {
93
+ return name in env.values;
94
+ }
95
+ isAlias(name1, name2) {
96
+ const u = env.variables[name2];
97
+ return u && u[name1] === 1;
98
+ }
99
+ get(name) {
100
+ return env.values[name];
101
+ }
102
+ // debugging
103
+ getAllValues() {
104
+ const values = this.values,
105
+ result = collectSymbols(values).map(k => ({name: k, value: values[k]}));
106
+ for (const k in values) {
107
+ result.push({name: k, value: values[k]});
108
+ }
109
+ return result;
110
+ }
111
+ }
112
+
113
+ // Custom unifier
114
+
115
+ class Unifier {}
116
+
117
+ const isUnifier = x => x instanceof Unifier;
118
+
119
+ // Unifier should define a method:
120
+ // unify(val, ls, rs, env):
121
+ // val is a value we are unifying with
122
+ // ls is a stack of left arguments
123
+ // rs is a stack of right arguments corresponding to ls
124
+ // env is an environment
125
+ // the result should be true/false for success/failure
126
+
127
+ // AnyVar
128
+
129
+ const _ = Symbol.for('deep6.any'),
130
+ any = _;
131
+
132
+ // Variable
133
+
134
+ class Variable extends Unifier {
135
+ constructor(name) {
136
+ super();
137
+ this.name = name || Symbol();
138
+ }
139
+ isBound(env) {
140
+ return this.name in env.values;
141
+ }
142
+ isAlias(name, env) {
143
+ const u = env.variables[this.name];
144
+ return u && u[name instanceof Variable ? name.name : name] === 1;
145
+ }
146
+ get(env) {
147
+ return env.values[this.name];
148
+ }
149
+ unify(val, ls, rs, env) {
150
+ if (this.name in env.values) {
151
+ // isBound
152
+ ls.push(env.values[this.name]);
153
+ rs.push(val);
154
+ return true;
155
+ }
156
+ if (val === _ || val === this) return true;
157
+ if (val instanceof Variable) {
158
+ if (val.name in env.values) {
159
+ // isBound
160
+ env.bindVal(this.name, env.values[val.name]);
161
+ return true;
162
+ }
163
+ env.bindVar(this.name, val.name);
164
+ return true;
165
+ }
166
+ env.bindVal(this.name, val);
167
+ return true;
168
+ }
169
+ }
170
+
171
+ const isVariable = x => x instanceof Variable,
172
+ variable = name => new Variable(name);
173
+
174
+ export {Env, Unifier, isUnifier, Variable, variable, isVariable, _, any};
@@ -0,0 +1,21 @@
1
+ import {any, _} from './env.js';
2
+ import unify from './unify.js';
3
+ import originalClone from './traverse/clone.js';
4
+ import preprocess from './traverse/preprocess.js';
5
+
6
+ const defaultOptions = {circular: true};
7
+ const equal = (a, b, options = defaultOptions) => !!unify(a, b, null, options);
8
+
9
+ const defaultMatchOptions = {openObjects: true, openMaps: true, openSets: true, circular: true};
10
+ const match = (object, pattern, options = defaultMatchOptions) =>
11
+ !!unify(object, preprocess(pattern, options), null, {
12
+ circular: options.circular,
13
+ symbols: options.symbols,
14
+ loose: options.loose,
15
+ ignoreFunctions: options.ignoreFunctions
16
+ });
17
+
18
+ const clone = (a, options = defaultOptions) => originalClone(a, null, options);
19
+
20
+ export {equal, clone, match, match as isShape, any, _};
21
+ export default equal;