Completed
Push — master ( 5b5b28...e6d81f )
by Esaú
01:58
created

spec/helpers/hierarchy-helper.js   F

Complexity

Total Complexity 119
Complexity/F 2.48

Size

Lines of Code 344
Function Count 48

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 0
wmc 119
c 4
b 0
f 0
nc 0
mnd 5
bc 74
fnc 48
dl 0
loc 344
rs 3.12
bpm 1.5416
cpm 2.4791
noi 0

2 Functions

Rating   Name   Duplication   Size   Complexity  
B hierarchy-helper.js ➔ ??? 0 311 2
C hierarchy-helper.js ➔ instanceDefinedOrNull 0 22 10

How to fix   Complexity   

Complexity

Complex classes like spec/helpers/hierarchy-helper.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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