Completed
Push — master ( cbd62b...1c64ed )
by Esaú
01:43
created

hierarchy-helper.js ➔ instanceDefinedOrNull   D

Complexity

Conditions 9
Paths 8

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 9
c 5
b 0
f 0
nc 8
dl 0
loc 28
rs 4.909
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
// spec/helpers/hierarchy-helper.js
2
"use strict";
3
4
// :: DEPENDENCIES
5
6
// load native dependencies
7
const path = require("path");
8
const root = path.dirname(path.dirname(__dirname));
9
10
// global variables
11
const noStr   = [{}, true, false, 42, 3.1416, -42, -3.1416, () => null];
12
const noNmb   = [{}, true, false, '', "qwerty", () => null];
13
const name    = "qwerty";
14
const message = "asdf";
15
16
if (typeof Symbol === "function") {
17
    noStr.push(Symbol("symbol"));
18
    noNmb.push(Symbol("symbol"));
19
}
20
21
module.exports = (hierarchy) => {
22
    // check parameters
23
    if (!Array.isArray(hierarchy)) {
24
        throw new Error("hierarchy must be an array");
25
    }
26
27
    // load dependencies
28
    let deps      = hierarchy.map(value => require(path.join(root, value + ".js")));
29
    let klassName = hierarchy.pop();
30
    let Klass     = deps.pop();
31
    const first   = (hierarchy.length === 0);
32
    const third   = (hierarchy.length >= 3);
33
34
    // :: TESTING
35
36
    // test the last class in the hierarchy tree
37
    describe(klassName, () => {
38
39
        // :: INHERITED PROTOTYPE
40
41
        // all inherit from Object
42
        it("should inherit from 'Object'", () => {
43
            expect(new Klass()).toEqual(jasmine.any(Object));
44
        });
45
46
        // check the hierarchy tree
47
        for (let i = 0; i < hierarchy.length; i += 1) {
48
            it("should inherit from '" + hierarchy[i] + "'", () => {
49
                expect(new Klass()).toEqual(jasmine.any(deps[i]));
50
            });
51
        }
52
53
        // check inherited properties
54
        if (!first) {
55
            it("should have a prototype property named 'name'", () => {
56
                expect(Klass.prototype).toHaveString("name");
57
            });
58
59
            it("should have a prototype property named 'message'", () => {
60
                expect(Klass.prototype).toHaveString("message");
61
            });
62
63
            it("should have a prototype property named 'code'", () => {
64
                expect(Klass.prototype).toHaveMember("code");
65
            });
66
        }
67
68
        // check Object methods
69
        it("should have a prototype method named 'toString()'", () => {
70
            expect(Klass.prototype).toHaveMethod("toString");
71
        });
72
73
        // check inherited methods
74
        if (!first) {
75
            it("should have a prototype method named 'native()'", () => {
76
                expect(Klass.prototype).toHaveMethod("native");
77
            });
78
        }
79
80
        // :: EXTENDED PROTOTYPE
81
82
        // check extended properties
83
        if (first) {
84
            it("should have a prototype property named 'name'", () => {
85
                expect(Klass.prototype).toHaveString("name");
86
            });
87
88
            it("should have a prototype property named 'message'", () => {
89
                expect(Klass.prototype).toHaveString("message");
90
            });
91
92
            it("should have a prototype property named 'code'", () => {
93
                expect(Klass.prototype).toHaveMember("code");
94
            });
95
96
            it("should have a prototype method named 'native()'", () => {
97
                expect(Klass.prototype).toHaveMethod("native");
98
            });
99
        }
100
101
        // :: PROTOTYPE VALUES
102
103
        it("should have the 'class' name in the prototype property named 'name'", () => {
104
            expect(Klass.prototype.name).toEqual(klassName);
105
        });
106
107
        it("should have a dummy default value as message", () => {
108
            expect(Klass.prototype.message).toEqual("thrown");
109
        });
110
111
        it("should have a null default value as code", () => {
112
            expect(Klass.prototype.code).toBeNull();
113
        });
114
115
        // :: CONSTRUCTOR
116
117
        it("should instantiate without parameters", () => {
118
            instanceNoParameters(Klass, testNoErrors, third);
119
            testNoErrors(() => new Klass());
120
        });
121
122
        it("should instantiate with parameters", () => {
123
            instanceParameters(Klass, testNoErrors, testNoErrors, testNoErrors, third);
124
        });
125
126
        // use the tests according to the hierarchy level
127
        if (third) {
128
            testThird(Klass);
129
        } else {
130
            test(Klass);
131
        }
132
133
    });
134
135
};
136
137
// Tests that a function doesn't throw any Error
138
function testNoErrors(fn) {
139
    expect(fn).not.toThrowError("parameter 'name' must be a 'string'");
140
    expect(fn).not.toThrowError("parameter 'message' must be a 'string'");
141
    expect(fn).not.toThrowError("parameter 'code' must be a 'number'");
142
}
143
144
// Tests instantiation of a class without parameters (undefined, null or none).
145
function instanceNoParameters(Klass, fn, third) {
146
    let arg1, arg2, arg3, test;
147
    test = (() => new Klass(arg1, arg2, arg3));
148
    for (let i = 0; i < 2; i += 1) {
149
        arg1 = (i % 2 === 0 ? undefined : null);
150
        for (let j = 0; j < 2; j += 1) {
151
            arg2 = (j % 2 === 0 ? undefined : null);
152
            if (third) {
153
                fn(test);
154
            } else {
155
                for (let e = 0; e < 2; e += 1) {
156
                    arg3 = (e % 2 === 0 ? undefined : null);
157
                    fn(test);
158
                }
159
            }
160
        }
161
    }
162
}
163
164
// Tests instantiation of a class with parameters.
165
function instanceParameters(Klass, fn1, fn2, fn3, third) {
166
    let arg1, arg2, arg3, test3, args1, args2, args3;
167
    const test1 = (() => new Klass(arg1));
168
    const test2 = (() => new Klass(arg1, arg2));
169
    if (third) {
170
        test3 = (() => null);
171
        args1 = [undefined, null, Klass.prototype.message];
172
        args2 = [undefined, null, Math.round(Math.random() * 0xFFFFFFFF)];
173
        args3 = [];
174
    } else {
175
        test3 = (() => new Klass(arg1, arg2, arg3));
176
        args1 = [undefined, null, Klass.prototype.name];
177
        args2 = [undefined, null, Klass.prototype.message];
178
        args3 = [undefined, null, Math.round(Math.random() * 0xFFFFFFFF)];
179
    }
180
    for (let i = 0; i < args1.length; i += 1) {
181
        arg1 = args1[i];
182
        fn1(test1);
183
        for (let j = 0; j < args2.length; j += 1) {
184
            arg2 = args2[j];
185
            fn2(test2);
186
            for (let e = 0; !third && e < args3.length; e += 1) {
187
                arg3 = args3[e];
188
                fn3(test3);
189
            }
190
        }
191
    }
192
}
193
194
// Loops for each parameter of the Klass constructor.
195
// If the iteration is even, the parameter is defined.
196
// If the iteration is odd, the parameter is null.
197
function instanceDefinedOrNull(Klass, name, message, code, fn1, fn2, fn3, third) {
198
    let opt1, opt2;
199
    if (third) {
200
        opt1 = message;
201
        opt2 = code;
202
    } else {
203
        opt1 = name;
204
        opt2 = message;
205
    }
206
    for (let i = 0; i < 2; i += 1) {
207
        const even1   = (i % 2 === 0);
208
        const arg1    = (even1 ? opt1 : null);
209
        const source1 = new Klass(arg1);
210
        fn1(source1, even1);
211
        for (let j = 0; j < 2; j += 1) {
212
            const even2   = (j % 2 === 0);
213
            const arg2    = (even2 ? opt2 : null);
214
            const source2 = new Klass(arg1, arg2);
215
            fn2(source2, even1, even2);
216
            for (let e = 0; !third && e < 2; e += 1) {
217
                const even3   = (e % 2 === 0);
218
                const arg3    = (even3 ? code : null);
219
                const source3 = new Klass(arg1, arg2, arg3);
220
                fn3(source3, even1, even2, even3);
221
            }
222
        }
223
    }
224
}
225
226
// Loops for each parameter of the Klass constructor using wrong types to test Error throwing.
227
function instanceThrowErrors(Klass, fn1, fn2, fn3, third) {
228
    let arg1, arg2, arg3, test33, test32, test31, test21, test22, test11, arr;
229
    if (third) {
230
        arr = noNmb;
231
        test33 = test32 = test31 = (() => null);
232
    } else {
233
        arr    = noStr;
234
        test33 = (() => new Klass(arg1, arg2, arg3));
235
        test32 = (() => new Klass(null, arg2, arg3));
236
        test31 = (() => new Klass(null, null, arg3));
237
    }
238
    test22 = (() => new Klass(arg1, arg2));
239
    test21 = (() => new Klass(null, arg2));
240
    test11 = (() => new Klass(arg1));
241
    for (let i = 0; i < noStr.length; i += 1) {
242
        arg1 = noStr[i];
243
        fn1(test11);
244
        for (let j = 0; j < arr.length; j += 1) {
245
            arg2 = arr[j];
246
            fn2(test21, test22);
247
            for (let e = 0; !third && e < noNmb.length; e += 1) {
248
                arg3 = noNmb[e];
249
                fn3(test31, test32, test33);
250
            }
251
        }
252
    }
253
}
254
255
// Tests classes that are a third level subclass, meaning that they require 2 arguments (message and code).
256
function testThird(Klass) {
257
258
    // :: CONSTRUCTOR
259
260
    it("should throw an Error if 'message' or 'code' are invalid parameters", () => {
261
        instanceThrowErrors(Klass, (test11) => {
262
            expect(test11).toThrowError("parameter 'message' must be a 'string'");
263
        }, (test21, test22) => {
264
            expect(test21).toThrowError("parameter 'code' must be a 'number'");
265
            expect(test22).toThrowError("parameter 'message' must be a 'string'");
266
        }, null, true);
267
    });
268
269
    // :: MEMBER PROPERTIES
270
271
    const message = "asdf";
272
    const code    = Math.round(Math.random() * 0xFFFFFFFF);
273
274
    it("should have all correct properties once instantiated", () => {
275
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
276
            if (even) {
277
                expect(instance.name).toEqual(Klass.prototype.name);
278
                expect(instance.message).toEqual(message);
279
            } else {
280
                expect(instance.name).toEqual(Klass.prototype.name);
281
                expect(instance.message).toEqual(Klass.prototype.message);
282
            }
283
            expect(instance.code).toBeNull();
284
        }, (instance, even1, even2) => {
285
            expect(instance.name).toEqual(Klass.prototype.name);
286
            expect(instance.message).toEqual(even1 ? message : Klass.prototype.message);
287
            expect(instance.code).toEqual(even2 ? code : null);
288
        }, null, true);
289
    });
290
291
    // :: MEMBER METHODS
292
293
    it("#toString()", () => {
294
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
295
            let exp;
296
            if (even) {
297
                exp = Klass.prototype.name + ": " + message + '.';
298
            } else {
299
                exp = Klass.prototype.name + ": " + Klass.prototype.message + '.';
300
            }
301
            expect(instance.toString()).toEqual(exp);
302
        }, (instance, even1, even2) => {
303
            let exp;
304
            exp = Klass.prototype.name;
305
            exp += (even2 ? " (0x" + code.toString(16) + "):" : ':' ) + ' ';
306
            exp += (even1 ? message : Klass.prototype.message) + '.';
307
            expect(instance.toString()).toEqual(exp);
308
        }, null, true);
309
    });
310
311
    it("#native()", () => {
312
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
313
            const exp = (even ? message : Klass.prototype.message);
314
            expect(instance.native()).toEqual(new Error(exp));
315
        }, (instance, even1) => {
316
            const exp = (even1 ? message : Klass.prototype.message);
317
            expect(instance.native()).toEqual(new Error(exp));
318
        }, null, true);
319
    });
320
321
}
322
323
// Tests classes that require 3 arguments (name, message and code).
324
function test(Klass) {
325
326
    // :: CONSTRUCTOR
327
328
    it("should throw an Error if 'message' or 'code' are invalid parameters", () => {
329
        instanceThrowErrors(Klass, (test11) => {
330
            expect(test11).toThrowError("parameter 'name' must be a 'string'");
331
        }, (test21, test22) => {
332
            expect(test22).toThrowError("parameter 'name' must be a 'string'");
333
            expect(test21).toThrowError("parameter 'message' must be a 'string'");
334
        }, (test31, test32, test33) => {
335
            expect(test33).toThrowError("parameter 'name' must be a 'string'");
336
            expect(test32).toThrowError("parameter 'message' must be a 'string'");
337
            expect(test31).toThrowError("parameter 'code' must be a 'number'");
338
        }, false);
339
    });
340
341
    // :: MEMBER PROPERTIES
342
343
    const code = Math.round(Math.random() * 0xFFFFFFFF);
344
345
    it("should have all correct properties once instantiated", () => {
346
        instanceDefinedOrNull(Klass, name, message, code, (instance, even) => {
347
            if (even) {
348
                expect(instance.name).toEqual(name);
349
                expect(instance.message).toEqual(Klass.prototype.message);
350
            } else {
351
                expect(instance.name).toEqual(Klass.prototype.name);
352
                expect(instance.message).toEqual(Klass.prototype.message);
353
            }
354
            expect(instance.code).toBeNull();
355
        }, (instance, even1, even2) => {
356
            expect(instance.name).toEqual(even1 ? name : Klass.prototype.name);
357
            expect(instance.message).toEqual(even2 ? message : Klass.prototype.message);
358
            expect(instance.code).toBeNull();
359
        }, (instance, even1, even2, even3) => {
360
            expect(instance.name).toEqual(even1 ? name : Klass.prototype.name);
361
            expect(instance.message).toEqual(even2 ? message : Klass.prototype.message);
362
            expect(instance.code).toEqual(even3 ? code : null);
363
        }, false);
364
    });
365
366
    // :: MEMBER METHODS
367
368
    it("#toString()", () => {
369
        instanceDefinedOrNull(Klass, name, message, code, (instance, even) => {
370
            let exp;
371
            if (even) {
372
                exp = name + ": " + Klass.prototype.message + '.';
373
            } else {
374
                exp = Klass.prototype.name + ": " + Klass.prototype.message + '.';
375
            }
376
            expect(instance.toString()).toEqual(exp);
377
        }, (instance, even1, even2) => {
378
            let exp;
379
            exp = (even1 ? name : Klass.prototype.name) + ':';
380
            exp += ' ' + (even2 ? message : Klass.prototype.message) + '.';
381
            expect(instance.toString()).toEqual(exp);
382
        }, (instance, even1, even2, even3) => {
383
            let exp;
384
            exp = (even1 ? name : Klass.prototype.name);
385
            exp += (even3 ? " (0x" + code.toString(16) + "):" : ':') + ' ';
386
            exp += (even2 ? message : Klass.prototype.message) + '.';
387
            expect(instance.toString()).toEqual(exp);
388
        }, false);
389
    });
390
391
    it("#native()", () => {
392
        instanceDefinedOrNull(Klass, name, message, code, (instance) => {
393
            const exp = Klass.prototype.message;
394
            expect(instance.native()).toEqual(new Error(exp));
395
        }, (instance, even1, even2) => {
396
            const exp = (even2 ? message : Klass.prototype.message);
397
            expect(instance.native()).toEqual(new Error(exp));
398
        }, (instance, even1, even2) => {
399
            const exp = (even2 ? message : Klass.prototype.message);
400
            expect(instance.native()).toEqual(new Error(exp));
401
        }, false);
402
    });
403
404
}