Parcourir la source

Global tests refactoring

sanex3339 il y a 8 ans
Parent
commit
d1e32d977d
48 fichiers modifiés avec 4804 ajouts et 2582 suppressions
  1. 0 0
      dist/index.js
  2. 7 7
      package.json
  3. 16 18
      src/cli/JavaScriptObfuscatorCLI.ts
  4. 1 1
      src/cli/utils/CLIUtils.ts
  5. 54 25
      test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts
  6. 40 20
      test/functional-tests/custom-nodes/domain-lock-nodes/DomainLockNode.spec.ts
  7. 45 14
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayCallsWrapper.spec.ts
  8. 42 20
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayNode.spec.ts
  9. 46 24
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.spec.ts
  10. 267 141
      test/functional-tests/javascript-obfuscator-cli/JavaScriptObfuscatorCLI.spec.ts
  11. 55 31
      test/functional-tests/javascript-obfuscator-internal/JavaScriptObfuscatorInternal.spec.ts
  12. 349 141
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  13. 325 170
      test/functional-tests/node-transformers/control-flow-transformers/block-statement-control-flow-transformer/BlockStatementControlFlowTransformer.spec.ts
  14. 62 29
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts
  15. 82 42
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts
  16. 95 48
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts
  17. 17 10
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec.ts
  18. 148 84
      test/functional-tests/node-transformers/control-flow-transformers/function-control-flow-transformer/FunctionControlFlowTransformer.spec.ts
  19. 94 42
      test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts
  20. 63 29
      test/functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec.ts
  21. 36 32
      test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec.ts
  22. 189 120
      test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts
  23. 45 27
      test/functional-tests/node-transformers/obfuscating-transformers/catch-clause-transformer/CatchClauseTransformer.spec.ts
  24. 52 26
      test/functional-tests/node-transformers/obfuscating-transformers/function-declaration-transformer/FunctionDeclarationTransformer.spec.ts
  25. 183 100
      test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec.ts
  26. 29 19
      test/functional-tests/node-transformers/obfuscating-transformers/labeled-statement-transformer/LabeledStatementTransformer.spec.ts
  27. 301 150
      test/functional-tests/node-transformers/obfuscating-transformers/literal-transformer/LiteralTransformer.spec.ts
  28. 40 21
      test/functional-tests/node-transformers/obfuscating-transformers/object-expression-transformer/ObjectExpressionTransformer.spec.ts
  29. 297 150
      test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec.ts
  30. 296 267
      test/functional-tests/stack-trace-analyzer/stack-trace-analyzer/StackTraceAnalyzer.spec.ts
  31. 72 49
      test/functional-tests/templates/custom-nodes/domain-lock-nodes/DomainLockNodeTemplate.spec.ts
  32. 43 20
      test/functional-tests/templates/custom-nodes/string-array-nodes/StringArrayCallsWrapperNodeTemplate.spec.ts
  33. 5 7
      test/performance-tests/JavaScriptObfuscatorPerformance.spec.ts
  34. 12 5
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  35. 38 22
      test/unit-tests/cli/cli-utils/CLIUtils.spec.ts
  36. 28 12
      test/unit-tests/decorators/initializable/Initializable.spec.ts
  37. 87 124
      test/unit-tests/node/node-appender/NodeAppender.spec.ts
  38. 174 86
      test/unit-tests/node/node-utils/NodeUtils.spec.ts
  39. 2 2
      test/unit-tests/obfuscation-result/ObfuscationResult.spec.ts
  40. 260 178
      test/unit-tests/options/options-normalizer/OptionsNormalizer.spec.ts
  41. 59 37
      test/unit-tests/source-map-corrector/SourceMapCorrector.spec.ts
  42. 52 10
      test/unit-tests/stack-trace-analyzer/stack-trace-analyzer/StackTraceAnalyzer.spec.ts
  43. 132 54
      test/unit-tests/storages/ArrayStorage.spec.ts
  44. 135 54
      test/unit-tests/storages/MapStorage.spec.ts
  45. 56 14
      test/unit-tests/utils/crypt-utils/CryptUtils.spec.ts
  46. 23 3
      test/unit-tests/utils/random-generator-utils/RandomGeneratorUtils.spec.ts
  47. 306 48
      test/unit-tests/utils/utils/Utils.spec.ts
  48. 44 49
      yarn.lock

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
dist/index.js


+ 7 - 7
package.json

@@ -37,29 +37,29 @@
   "devDependencies": {
     "@types/chai": "4.0.0",
     "@types/chance": "0.7.33",
-    "@types/commander": "2.3.31",
+    "@types/commander": "2.9.1",
     "@types/escodegen": "0.0.6",
     "@types/esprima": "2.1.33",
     "@types/estraverse": "0.0.6",
     "@types/estree": "0.0.35",
     "@types/mkdirp": "0.3.29",
     "@types/mocha": "2.2.41",
-    "@types/node": "7.0.27",
-    "@types/sinon": "2.3.0",
+    "@types/node": "7.0.29",
+    "@types/sinon": "2.3.1",
     "@types/string-template": "1.0.2",
     "awesome-typescript-loader": "3.1.3",
     "babel-cli": "6.24.1",
     "babel-loader": "7.0.0",
     "babel-plugin-array-includes": "2.0.3",
     "babel-preset-es2015": "6.24.1",
-    "chai": "4.0.1",
+    "chai": "4.0.2",
     "coveralls": "2.13.1",
     "istanbul": "1.1.0-alpha.1",
     "mocha": "3.4.2",
     "pre-commit": "1.2.2",
-    "sinon": "2.3.2",
-    "ts-node": "3.0.4",
-    "tslint": "5.4.2",
+    "sinon": "2.3.3",
+    "ts-node": "3.0.6",
+    "tslint": "5.4.3",
     "tslint-loader": "3.5.3",
     "typescript": "2.3.4",
     "webpack": "2.6.1",

+ 16 - 18
src/cli/JavaScriptObfuscatorCLI.ts

@@ -11,7 +11,7 @@ import { BooleanSanitizer } from './sanitizers/BooleanSanitizer';
 import { SourceMapModeSanitizer } from './sanitizers/SourceMapModeSanitizer';
 import { StringArrayEncodingSanitizer } from './sanitizers/StringArrayEncodingSanitizer';
 
-import { CLIUtils } from './CLIUtils';
+import { CLIUtils } from './utils/CLIUtils';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 
 export class JavaScriptObfuscatorCLI {
@@ -21,14 +21,14 @@ export class JavaScriptObfuscatorCLI {
     private readonly arguments: string[];
 
     /**
-     * @type {commander.ICommand}
+     * @type {string[]}
      */
-    private commands: commander.ICommand;
+    private readonly rawArguments: string[];
 
     /**
-     * @type {string}
+     * @type {commander.CommanderStatic}
      */
-    private data: string = '';
+    private commands: commander.CommanderStatic;
 
     /**
      * @type {string}
@@ -36,9 +36,9 @@ export class JavaScriptObfuscatorCLI {
     private inputPath: string;
 
     /**
-     * @type {string[]}
+     * @type {string}
      */
-    private rawArguments: string[];
+    private sourceCode: string = '';
 
     /**
      * @param argv
@@ -46,17 +46,13 @@ export class JavaScriptObfuscatorCLI {
     constructor (argv: string[]) {
         this.rawArguments = argv;
         this.arguments = this.rawArguments.slice(2);
-    }
 
-    /**
-     * @returns {string}
-     */
-    private static getBuildVersion (): string {
-        return CLIUtils.getPackageConfig().version;
+        this.commands = <commander.CommanderStatic>(new commander.Command());
     }
 
     public run (): void {
         this.configureCommands();
+        this.configureHelp();
 
         if (!this.arguments.length || this.arguments.includes('--help')) {
             this.commands.outputHelp();
@@ -97,8 +93,8 @@ export class JavaScriptObfuscatorCLI {
     }
 
     private configureCommands (): void {
-        this.commands = new commander.Command()
-            .version(JavaScriptObfuscatorCLI.getBuildVersion(), '-v, --version')
+        this.commands
+            .version(CLIUtils.getPackageConfig().version, '-v, --version')
             .usage('<inputPath> [options]')
             .option(
                 '-o, --output <path>',
@@ -211,7 +207,9 @@ export class JavaScriptObfuscatorCLI {
                 BooleanSanitizer
             )
             .parse(this.rawArguments);
+    }
 
+    private configureHelp (): void {
         this.commands.on('--help', () => {
             console.log('  Examples:\n');
             console.log('    %> javascript-obfuscator in.js --compact true --selfDefending false');
@@ -221,7 +219,7 @@ export class JavaScriptObfuscatorCLI {
     }
 
     private getData (): void {
-        this.data = CLIUtils.readFile(this.inputPath);
+        this.sourceCode = CLIUtils.readFile(this.inputPath);
     }
 
     private processData (): void {
@@ -240,7 +238,7 @@ export class JavaScriptObfuscatorCLI {
      * @param options
      */
     private processDataWithoutSourceMap (outputCodePath: string, options: TInputOptions): void {
-        const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(this.data, options).getObfuscatedCode();
+        const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(this.sourceCode, options).getObfuscatedCode();
 
         CLIUtils.writeFile(outputCodePath, obfuscatedCode);
     }
@@ -260,7 +258,7 @@ export class JavaScriptObfuscatorCLI {
             sourceMapFileName: path.basename(outputSourceMapPath)
         };
 
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(this.data, options);
+        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(this.sourceCode, options);
 
         CLIUtils.writeFile(outputCodePath, obfuscationResult.getObfuscatedCode());
 

+ 1 - 1
src/cli/CLIUtils.ts → src/cli/utils/CLIUtils.ts

@@ -2,7 +2,7 @@ import * as fs from 'fs';
 import * as mkdirp from 'mkdirp';
 import * as path from 'path';
 
-import { IPackageConfig } from '../interfaces/IPackageConfig';
+import { IPackageConfig } from '../../interfaces/IPackageConfig';
 
 export class CLIUtils {
     /**

+ 54 - 25
test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts

@@ -13,32 +13,61 @@ describe('ConsoleOutputDisableExpressionNode', () => {
     const consoleErrorRegExp: RegExp = /_0x([a-f0-9]){4,6}\['console'\]\['error'\] *= *_0x([a-f0-9]){4,6};/u;
     const consoleWarnRegExp: RegExp = /_0x([a-f0-9]){4,6}\['console'\]\['warn'\] *= *_0x([a-f0-9]){4,6};/u;
 
-    it('should correctly append `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is set', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                disableConsoleOutput: true
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(), consoleLogRegExp);
-        assert.match(obfuscationResult.getObfuscatedCode(), consoleErrorRegExp);
-        assert.match(obfuscationResult.getObfuscatedCode(), consoleWarnRegExp);
+    describe('`disableConsoleOutput` option is set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    disableConsoleOutput: true
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('match #1: should correctly append custom node into the obfuscated code', () => {
+            assert.match(obfuscatedCode, consoleLogRegExp);
+        });
+
+        it('match #2: should correctly append custom node into the obfuscated code', () => {
+            assert.match(obfuscatedCode, consoleErrorRegExp);
+        });
+
+        it('match #3: should correctly append custom node into the obfuscated code', () => {
+            assert.match(obfuscatedCode, consoleWarnRegExp);
+        });
     });
 
-    it('should\'t append `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is not set', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                disableConsoleOutput: false,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.notMatch(obfuscationResult.getObfuscatedCode(), consoleLogRegExp);
-        assert.notMatch(obfuscationResult.getObfuscatedCode(), consoleErrorRegExp);
-        assert.notMatch(obfuscationResult.getObfuscatedCode(), consoleWarnRegExp);
+    describe('`disableConsoleOutput` option isn\'t set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    disableConsoleOutput: false
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('match #1: shouldn\'t append custom node into the obfuscated code', () => {
+            assert.notMatch(obfuscatedCode, consoleLogRegExp);
+        });
+
+        it('match #2: shouldn\'t append custom node into the obfuscated code', () => {
+            assert.notMatch(obfuscatedCode, consoleErrorRegExp);
+        });
+
+        it('match #3: shouldn\'t append custom node into the obfuscated code', () => {
+            assert.notMatch(obfuscatedCode, consoleWarnRegExp);
+        });
     });
 });

+ 40 - 20
test/functional-tests/custom-nodes/domain-lock-nodes/DomainLockNode.spec.ts

@@ -9,27 +9,47 @@ import { readFileAsString } from '../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 describe('DomainLockNode', () => {
-    it('should correctly append `DomainLockNode` custom node into the obfuscated code if `domainLock` option is set', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                domainLock: ['.example.com']
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(), /var _0x([a-f0-9]){4,6} *= *new RegExp/);
+    const regExp: RegExp = /var _0x([a-f0-9]){4,6} *= *new RegExp/;
+
+    describe('`domainLock` option is set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    domainLock: ['.example.com']
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should correctly append custom node into the obfuscated code', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 
-    it('should\'t append `DomainLockNode` custom node into the obfuscated code if `domainLock` option is not set', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                domainLock: []
-            }
-        );
-
-        assert.notMatch(obfuscationResult.getObfuscatedCode(), /var _0x([a-f0-9]){4,6} *= *new RegExp/);
+    describe('`domainLock` option isn\'t set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    domainLock: []
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('shouldn\'t append custom node into the obfuscated code', () => {
+            assert.notMatch(obfuscatedCode, regExp);
+        });
     });
 });

+ 45 - 14
test/functional-tests/custom-nodes/string-array-nodes/StringArrayCallsWrapper.spec.ts

@@ -9,19 +9,50 @@ import { readFileAsString } from '../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 describe('StringArrayCallsWrapper', () => {
-    it('should correctly append `StringArrayCallsWrapper` custom node into the obfuscated code', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                stringArray: true,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.match(
-            obfuscationResult.getObfuscatedCode(),
-            /_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6} *- *0x0\;/
-        );
+    const regExp: RegExp = /_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6} *- *0x0\;/;
+
+    describe('`stringArray` option is set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should correctly append custom node into the obfuscated code', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
+    });
+
+    describe('`stringArray` option isn\'t set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    stringArray: false
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('shouldn\'t append custom node into the obfuscated code', () => {
+            assert.notMatch(obfuscatedCode, regExp);
+        });
     });
 });

+ 42 - 20
test/functional-tests/custom-nodes/string-array-nodes/StringArrayNode.spec.ts

@@ -9,28 +9,50 @@ import { readFileAsString } from '../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 describe('StringArrayNode', () => {
-    it('should correctly append `StringArrayNode` custom node into the obfuscated code if `stringArray` option is set', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                stringArray: true,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(), /^var _0x([a-f0-9]){4} *= *\[/);
+    const regExp: RegExp = /^var _0x([a-f0-9]){4} *= *\[/;
+
+    describe('`stringArray` option is set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should correctly append custom node into the obfuscated code', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 
-    it('should\'t append `StringArrayNode` custom node into the obfuscated code if `stringArray` option is not set', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                stringArray: false
-            }
-        );
+    describe('`stringArray` option isn\'t set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    stringArray: false
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
 
-        assert.notMatch(obfuscationResult.getObfuscatedCode(), /^var _0x([a-f0-9]){4} *= *\[/);
+        it('shouldn\'t append custom node into the obfuscated code', () => {
+            assert.notMatch(obfuscatedCode, regExp);
+        });
     });
 });

+ 46 - 24
test/functional-tests/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.spec.ts

@@ -9,31 +9,53 @@ import { readFileAsString } from '../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 describe('StringArrayRotateFunctionNode', () => {
-    it('should correctly append `StringArrayRotateFunctionNode` custom node into the obfuscated code if `rotateStringArray` option is set', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                rotateStringArray: true,
-                stringArray: true,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(), /while *\(-- *_0x([a-f0-9]){4,6}\) *\{/);
+    const regExp: RegExp = /while *\(-- *_0x([a-f0-9]){4,6}\) *\{/;
+
+    describe('`stringArray` option is set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    rotateStringArray: true,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should correctly append custom node into the obfuscated code', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 
-    it('should\'t append `StringArrayRotateFunctionNode` custom node into the obfuscated code if `rotateStringArray` option is not set', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                rotateStringArray: false,
-                stringArray: true,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.notMatch(obfuscationResult.getObfuscatedCode(), /while *\(-- *_0x([a-f0-9]){4,6}\) *\{/);
+    describe('`stringArray` option isn\'t set', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    rotateStringArray: false,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('shouldn\'t append custom node into the obfuscated code', () => {
+            assert.notMatch(obfuscatedCode, regExp);
+        });
     });
 });

+ 267 - 141
test/functional-tests/javascript-obfuscator-cli/JavaScriptObfuscatorCLI.spec.ts

@@ -9,22 +9,24 @@ import { StdoutWriteMock } from '../../mocks/StdoutWriteMock';
 import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
 
 describe('JavaScriptObfuscatorCLI', function (): void {
-    let fixturesDirName: string = 'test/fixtures',
-        fixtureFileName: string = 'sample.js',
-        fixtureFilePath: string = `${fixturesDirName}/${fixtureFileName}`,
-        outputDirName: string = 'test/tmp',
-        outputFileName: string = 'sample-obfuscated.js',
-        outputFilePath: string = `${outputDirName}/${outputFileName}`;
+    this.timeout(100000);
 
-    this.timeout(5000);
+    const fixturesDirName: string = 'test/fixtures';
+    const fixtureFileName: string = 'sample.js';
+    const fixtureFilePath: string = `${fixturesDirName}/${fixtureFileName}`;
+    const outputDirName: string = 'test/tmp';
+    const outputFileName: string = 'sample-obfuscated.js';
+    const outputFilePath: string = `${outputDirName}/${outputFileName}`;
 
     describe('run (): void', () => {
         before(() => {
             mkdirp.sync(outputDirName);
         });
 
-        describe('--output option is set', () => {
-            it('should creates file with obfuscated JS code in --output directory', () => {
+        describe('`--output` option is set', () => {
+            let isFileExist: boolean;
+
+            before(() => {
                 JavaScriptObfuscator.runCLI([
                     'node',
                     'javascript-obfuscator',
@@ -37,153 +39,259 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     '0'
                 ]);
 
-                assert.equal(fs.existsSync(outputFilePath), true);
+                isFileExist = fs.existsSync(outputFilePath);
             });
 
-            afterEach(() => {
-                fs.unlinkSync(outputFilePath);
-            });
-        });
-
-        describe('--output option is not set', () => {
-            it(`should creates file called \`${outputFileName}\` with obfuscated JS code in \`${fixturesDirName}\` directory`, () => {
-                let outputFixturesFilePath: string = `${fixturesDirName}/${outputFileName}`;
-
-                JavaScriptObfuscator.runCLI([
-                    'node',
-                    'javascript-obfuscator',
-                    fixtureFilePath
-                ]);
-
-                assert.equal(fs.existsSync(outputFixturesFilePath), true);
-
-                fs.unlinkSync(outputFixturesFilePath);
+            it('should create file with obfuscated code in `--output` directory', () => {
+                assert.equal(isFileExist, true);
             });
 
-            it(`should throw an error if input path is not a valid file path`, () => {
-                assert.throws(() => JavaScriptObfuscator.runCLI([
-                    'node',
-                    'javascript-obfuscator',
-                    'wrong/file/path'
-                ]), ReferenceError);
-            });
-
-            it(`should throw an error if input file extension is not a .js extension`, () => {
-                let outputWrongExtensionFileName: string = 'sample-obfuscated.ts',
-                    outputWrongExtensionFilePath: string = `${outputDirName}/${outputWrongExtensionFileName}`;
-
-                fs.writeFileSync(outputWrongExtensionFilePath, 'data');
-
-                assert.throws(() => JavaScriptObfuscator.runCLI([
-                    'node',
-                    'javascript-obfuscator',
-                    outputWrongExtensionFilePath
-                ]), ReferenceError);
-
-                fs.unlinkSync(outputWrongExtensionFilePath);
+            after(() => {
+                fs.unlinkSync(outputFilePath);
             });
         });
 
-        describe('--sourceMap option is set', () => {
-            let outputSourceMapPath: string = `${outputFilePath}.map`;
+        describe('`--output` option isn\'t set', () => {
+            describe('variant #1: default behaviour', () => {
+                let outputFixturesFilePath: string,
+                    isFileExist: boolean;
+
+                before(() => {
+                    outputFixturesFilePath = `${fixturesDirName}/${outputFileName}`;
 
-            describe('--sourceMapMode option is `separate`', () => {
-                it('should creates file with source map in the same directory as output file', () => {
                     JavaScriptObfuscator.runCLI([
                         'node',
                         'javascript-obfuscator',
-                        fixtureFilePath,
-                        '--output',
-                        outputFilePath,
-                        '--compact',
-                        'true',
-                        '--selfDefending',
-                        '0',
-                        '--sourceMap',
-                        'true'
+                        fixtureFilePath
                     ]);
 
-                    assert.equal(fs.existsSync(outputSourceMapPath), true);
+                    isFileExist = fs.existsSync(outputFixturesFilePath);
+                });
 
-                    const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }),
-                        sourceMap: any = JSON.parse(content);
+                it(`should create file \`${outputFileName}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => {
+                    assert.equal(isFileExist, true);
+                });
 
-                    assert.property(sourceMap, 'version');
-                    assert.property(sourceMap, 'sources');
-                    assert.property(sourceMap, 'names');
+                after(() => {
+                    fs.unlinkSync(outputFixturesFilePath);
                 });
+            });
 
-                it('should creates file with source map in the same directory as output file if `sourceMapBaseUrl` is set', () => {
-                    JavaScriptObfuscator.runCLI([
+            describe('variant #2: invalid input file path', () => {
+                const expectedError: ReferenceErrorConstructor = ReferenceError;
+
+                let testFunc: () => void;
+
+                before(() => {
+                    testFunc = () => JavaScriptObfuscator.runCLI([
                         'node',
                         'javascript-obfuscator',
-                        fixtureFilePath,
-                        '--output',
-                        outputFilePath,
-                        '--compact',
-                        'true',
-                        '--selfDefending',
-                        '0',
-                        '--sourceMap',
-                        'true',
-                        '--sourceMapBaseUrl',
-                        'http://localhost:9000/'
+                        'wrong/file/path'
                     ]);
-
-                    assert.equal(fs.existsSync(outputSourceMapPath), true);
-
-                    const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }),
-                        sourceMap: any = JSON.parse(content);
-
-                    assert.property(sourceMap, 'version');
-                    assert.property(sourceMap, 'sources');
-                    assert.property(sourceMap, 'names');
                 });
 
-                afterEach(() => {
-                    fs.unlinkSync(outputSourceMapPath);
+                it(`should throw an error`, () => {
+                    assert.throws(testFunc, expectedError);
                 });
             });
 
-            describe('--sourceMapFileName option is set', () => {
-                let sourceMapFileName: string = 'test',
-                    sourceMapFilePath: string = `${sourceMapFileName}.js.map`,
-                    outputSourceMapFilePath: string = `${outputDirName}/${sourceMapFilePath}`;
+            describe('variant #3: input file extension isn\'t `.js`', () => {
+                const expectedError: ReferenceErrorConstructor = ReferenceError;
+                const outputFileName: string = 'sample-obfuscated.ts';
+                const outputFilePath: string = `${outputDirName}/${outputFileName}`;
 
-                it('should creates source map file with given name in the same directory as output file', () => {
-                    JavaScriptObfuscator.runCLI([
+                let testFunc: () => void;
+
+                before(() => {
+                    fs.writeFileSync(outputFilePath, 'data');
+
+                    testFunc = () => JavaScriptObfuscator.runCLI([
                         'node',
                         'javascript-obfuscator',
-                        fixtureFilePath,
-                        '--output',
-                        outputFilePath,
-                        '--compact',
-                        'true',
-                        '--selfDefending',
-                        '0',
-                        '--sourceMap',
-                        'true',
-                        '--sourceMapFileName',
-                        sourceMapFileName
+                        outputFilePath
                     ]);
+                });
+
+                it(`should throw an error`, () => {
+                    assert.throws(testFunc, expectedError);
+                });
 
-                    assert.equal(fs.existsSync(outputSourceMapFilePath), true);
+                after(() => {
+                    fs.unlinkSync(outputFilePath);
+                });
+            });
+        });
 
-                    const content: string = fs.readFileSync(outputSourceMapFilePath, { encoding: 'utf8' }),
-                        sourceMap: any = JSON.parse(content);
+        describe('`--sourceMap` option is set', () => {
+            const outputSourceMapPath: string = `${outputFilePath}.map`;
+
+            describe('variant #1: `--sourceMapMode` option value is `separate`', () => {
+                describe('variant #1: default behaviour', () => {
+                    let isFileExist: boolean,
+                        sourceMapObject: any;
+
+                    before(() => {
+                        JavaScriptObfuscator.runCLI([
+                            'node',
+                            'javascript-obfuscator',
+                            fixtureFilePath,
+                            '--output',
+                            outputFilePath,
+                            '--compact',
+                            'true',
+                            '--selfDefending',
+                            '0',
+                            '--sourceMap',
+                            'true'
+                        ]);
+
+                        try {
+                            const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' });
+
+                            isFileExist = true;
+                            sourceMapObject = JSON.parse(content);
+                        } catch (e) {
+                            isFileExist = false;
+                        }
+                    });
+
+                    it('should create file with source map in the same directory as output file', () => {
+                        assert.equal(isFileExist, true);
+                    });
+
+                    it('source map from created file should contains property `version`', () => {
+                        assert.property(sourceMapObject, 'version');
+                    });
+
+                    it('source map from created file should contains property `sources`', () => {
+                        assert.property(sourceMapObject, 'sources');
+                    });
+
+                    it('source map from created file should contains property `names`', () => {
+                        assert.property(sourceMapObject, 'names');
+                    });
+
+                    after(() => {
+                        fs.unlinkSync(outputFilePath);
+                        fs.unlinkSync(outputSourceMapPath);
+                    });
+                });
 
-                    assert.property(sourceMap, 'version');
-                    assert.property(sourceMap, 'sources');
-                    assert.property(sourceMap, 'names');
+                describe('variant #2: `sourceMapBaseUrl` option is set', () => {
+                    let isFileExist: boolean,
+                        sourceMapObject: any;
+
+                    before(() => {
+                        JavaScriptObfuscator.runCLI([
+                            'node',
+                            'javascript-obfuscator',
+                            fixtureFilePath,
+                            '--output',
+                            outputFilePath,
+                            '--compact',
+                            'true',
+                            '--selfDefending',
+                            '0',
+                            '--sourceMap',
+                            'true',
+                            '--sourceMapBaseUrl',
+                            'http://localhost:9000/'
+                        ]);
+
+                        try {
+                            const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' });
+
+                            isFileExist = true;
+                            sourceMapObject = JSON.parse(content);
+                        } catch (e) {
+                            isFileExist = false;
+                        }
+                    });
+
+                    it('should create file with source map in the same directory as output file', () => {
+                        assert.equal(isFileExist, true);
+                    });
+
+                    it('source map from created file should contains property `version`', () => {
+                        assert.property(sourceMapObject, 'version');
+                    });
+
+                    it('source map from created file should contains property `sources`', () => {
+                        assert.property(sourceMapObject, 'sources');
+                    });
+
+                    it('source map from created file should contains property `names`', () => {
+                        assert.property(sourceMapObject, 'names');
+                    });
+
+                    after(() => {
+                        fs.unlinkSync(outputFilePath);
+                        fs.unlinkSync(outputSourceMapPath);
+                    });
                 });
 
-                afterEach(() => {
-                    fs.unlinkSync(outputSourceMapFilePath);
+                describe('variant #3: `--sourceMapFileName` option is set', () => {
+                    const sourceMapFileName: string = 'test';
+                    const sourceMapFilePath: string = `${sourceMapFileName}.js.map`;
+                    const outputSourceMapFilePath: string = `${outputDirName}/${sourceMapFilePath}`;
+
+                    let isFileExist: boolean,
+                        sourceMapObject: any;
+
+                    before(() => {
+                        JavaScriptObfuscator.runCLI([
+                            'node',
+                            'javascript-obfuscator',
+                            fixtureFilePath,
+                            '--output',
+                            outputFilePath,
+                            '--compact',
+                            'true',
+                            '--selfDefending',
+                            '0',
+                            '--sourceMap',
+                            'true',
+                            '--sourceMapFileName',
+                            sourceMapFileName
+                        ]);
+
+                        try {
+                            const content: string = fs.readFileSync(outputSourceMapFilePath, { encoding: 'utf8' });
+
+                            isFileExist = true;
+                            sourceMapObject = JSON.parse(content);
+                        } catch (e) {
+                            isFileExist = false;
+                        }
+                    });
+
+                    it('should create source map file with given name in the same directory as output file', () => {
+                        assert.equal(isFileExist, true);
+                    });
+
+                    it('source map from created file should contains property `version`', () => {
+                        assert.property(sourceMapObject, 'version');
+                    });
+
+                    it('source map from created file should contains property `sources`', () => {
+                        assert.property(sourceMapObject, 'sources');
+                    });
+
+                    it('source map from created file should contains property `names`', () => {
+                        assert.property(sourceMapObject, 'names');
+                    });
+
+                    after(() => {
+                        fs.unlinkSync(outputFilePath);
+                        fs.unlinkSync(outputSourceMapFilePath);
+                    });
                 });
             });
 
-            describe('--sourceMapMode option is `inline`', () => {
-                it('shouldn\'t create file with source map if `sourceMapMode` is `inline`', () => {
+            describe('variant #2: `--sourceMapMode` option is `inline`', () => {
+                let isFileExist: boolean;
+
+                before(() => {
                     JavaScriptObfuscator.runCLI([
                         'node',
                         'javascript-obfuscator',
@@ -200,12 +308,16 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                         'inline'
                     ]);
 
-                    assert.equal(fs.existsSync(outputSourceMapPath), false);
+                    isFileExist = fs.existsSync(outputSourceMapPath);
                 });
-            });
 
-            afterEach(() => {
-                fs.unlinkSync(outputFilePath);
+                it('shouldn\'t create file with source map', () => {
+                    assert.equal(isFileExist, false);
+                });
+
+                after(() => {
+                    fs.unlinkSync(outputFilePath);
+                });
             });
         });
 
@@ -218,32 +330,46 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 stdoutWriteMock = new StdoutWriteMock(process.stdout.write);
             });
 
-            it('should print `console.log` help if `--help` option is set', () => {
-                stdoutWriteMock.mute();
+            describe('`--help` option is set', () => {
+                let isConsoleLogCalled: boolean;
 
-                JavaScriptObfuscator.runCLI([
-                    'node',
-                    'javascript-obfuscator',
-                    fixtureFilePath,
-                    '--help'
-                ]);
+                beforeEach(() => {
+                    stdoutWriteMock.mute();
+
+                    JavaScriptObfuscator.runCLI([
+                        'node',
+                        'javascript-obfuscator',
+                        fixtureFilePath,
+                        '--help'
+                    ]);
 
-                stdoutWriteMock.restore();
+                    stdoutWriteMock.restore();
+                    isConsoleLogCalled = callback.called;
+                });
 
-                assert.equal(callback.called, true);
+                it('should print `console.log` help', () => {
+                    assert.equal(isConsoleLogCalled, true);
+                });
             });
 
-            it('should print `console.log` help if no options is passed', () => {
-                stdoutWriteMock.mute();
+            describe('no arguments passed', () => {
+                let isConsoleLogCalled: boolean;
 
-                JavaScriptObfuscator.runCLI([
-                    'node',
-                    'javascript-obfuscator'
-                ]);
+                beforeEach(() => {
+                    stdoutWriteMock.mute();
 
-                stdoutWriteMock.restore();
+                    JavaScriptObfuscator.runCLI([
+                        'node',
+                        'javascript-obfuscator'
+                    ]);
 
-                assert.equal(callback.called, true);
+                    stdoutWriteMock.restore();
+                    isConsoleLogCalled = callback.called;
+                });
+
+                it('should print `console.log` help', () => {
+                    assert.equal(isConsoleLogCalled, true);
+                });
             });
 
             afterEach(() => {

+ 55 - 31
test/functional-tests/javascript-obfuscator-internal/JavaScriptObfuscatorInternal.spec.ts

@@ -12,48 +12,72 @@ import { InversifyContainerFacade } from '../../../src/container/InversifyContai
 
 describe('JavaScriptObfuscatorInternal', () => {
     describe(`setSourceMapUrl (url: string)`, () => {
+        const code: string = 'var test = 1;';
+        const sourceMapUrl: string = 'test.js.map';
+
         let inversifyContainerFacade: IInversifyContainerFacade,
-            javaScriptObfuscator: IJavaScriptObfuscator,
-            obfuscationResult: IObfuscationResult,
-            sourceMapUrl: string = 'test.js.map';
-
-        it('should link obfuscated code with source map', () => {
-            inversifyContainerFacade = new InversifyContainerFacade({
-                ...NO_CUSTOM_NODES_PRESET,
-                sourceMap: true,
-                sourceMapFileName: sourceMapUrl
+            javaScriptObfuscator: IJavaScriptObfuscator;
+
+        describe('variant #1: default behaviour', () => {
+            const regExp: RegExp = new RegExp(`sourceMappingURL=${sourceMapUrl}`);
+
+            let obfuscatedCode: string,
+                sourceMapObject: any;
+
+            before(() => {
+                inversifyContainerFacade = new InversifyContainerFacade({
+                    ...NO_CUSTOM_NODES_PRESET,
+                    sourceMap: true,
+                    sourceMapFileName: sourceMapUrl
+                });
+                javaScriptObfuscator = inversifyContainerFacade
+                    .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
+
+                const obfuscationResult: IObfuscationResult = javaScriptObfuscator.obfuscate(code);
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                sourceMapObject = JSON.parse(obfuscationResult.getSourceMap());
             });
-            javaScriptObfuscator = inversifyContainerFacade
-                .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
 
-            obfuscationResult = javaScriptObfuscator.obfuscate('var test = 1;');
+            it('should link obfuscated code with source map', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                new RegExp(`sourceMappingURL=${sourceMapUrl}`))
-            ;
-            assert.isOk(JSON.parse(obfuscationResult.getSourceMap()).mappings);
+            it('should return valid source map with `mappings` property', () => {
+                assert.isOk(sourceMapObject.mappings);
+            });
         });
 
-        it('should properly add base url to source map import inside obfuscated code if `sourceMapBaseUrl` is set', () => {
+        describe('variant #2: `sourceMapBaseUrl` is set', () => {
             const sourceMapBaseUrl: string = 'http://localhost:9000';
+            const regExp: RegExp = new RegExp(`sourceMappingURL=${sourceMapBaseUrl}/${sourceMapUrl}$`);
+
+            let obfuscatedCode: string,
+                sourceMapObject: any;
+
+            before(() => {
+                inversifyContainerFacade = new InversifyContainerFacade({
+                    ...NO_CUSTOM_NODES_PRESET,
+                    sourceMap: true,
+                    sourceMapBaseUrl: sourceMapBaseUrl,
+                    sourceMapFileName: sourceMapUrl
+                });
+                javaScriptObfuscator = inversifyContainerFacade
+                    .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
 
-            inversifyContainerFacade = new InversifyContainerFacade({
-                ...NO_CUSTOM_NODES_PRESET,
-                sourceMap: true,
-                sourceMapBaseUrl: sourceMapBaseUrl,
-                sourceMapFileName: sourceMapUrl
+                const obfuscationResult: IObfuscationResult = javaScriptObfuscator.obfuscate(code);
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                sourceMapObject = JSON.parse(obfuscationResult.getSourceMap());
             });
-            javaScriptObfuscator = inversifyContainerFacade
-                .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
 
-            obfuscationResult = javaScriptObfuscator.obfuscate('var test = 1;');
+            it('should properly add base url to source map import inside obfuscated code', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                new RegExp(`sourceMappingURL=${sourceMapBaseUrl}/${sourceMapUrl}$`))
-            ;
-            assert.isOk(JSON.parse(obfuscationResult.getSourceMap()).mappings);
+            it('should return valid source map with `mappings` property', () => {
+                assert.isOk(sourceMapObject.mappings);
+            });
         });
     });
 });

+ 349 - 141
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -16,210 +16,418 @@ describe('JavaScriptObfuscator', () => {
             RandomGeneratorUtils.initializeRandomGenerator(0);
         });
 
-        describe('if `sourceMap` option is `false`', () => {
-            it('should returns object with obfuscated code and empty source map', () => {
-                let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/simple-input-1.js'),
+        describe('correct source code', () => {
+            let obfuscatedCode: string,
+                sourceMap: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
                     {
                         ...NO_CUSTOM_NODES_PRESET
                     }
                 );
 
-                assert.isOk(obfuscationResult.getObfuscatedCode());
-                assert.isNotOk(obfuscationResult.getSourceMap());
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                sourceMap = obfuscationResult.getSourceMap();
             });
-        });
 
-        describe('if `sourceMap` option is `true`', () => {
-            it('should returns object with obfuscated code and source map', () => {
-                let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/simple-input-1.js'),
-                    {
-                        ...NO_CUSTOM_NODES_PRESET,
-                        sourceMap: true
-                    }
-                );
+            it('should return correct obfuscated code', () => {
+                assert.isOk(obfuscatedCode);
+            });
 
-                assert.isOk(obfuscationResult.getObfuscatedCode());
-                assert.isOk(JSON.parse(obfuscationResult.getSourceMap()).mappings);
+            it('should return empty source map', () => {
+                assert.isNotOk(sourceMap);
             });
+        });
 
-            it('should returns object with obfuscated code with inline source map as Base64 string', () => {
-                let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/simple-input-1.js'),
-                    {
-                        ...NO_CUSTOM_NODES_PRESET,
-                        sourceMap: true,
-                        sourceMapMode: 'inline'
-                    }
-                );
+        describe('empty source code', () => {
+            let obfuscatedCode: string;
 
-                assert.isOk(obfuscationResult.getObfuscatedCode());
-                assert.match(
-                    obfuscationResult.getObfuscatedCode(),
-                    /sourceMappingURL=data:application\/json;base64/
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
                 );
-                assert.isOk(obfuscationResult.getSourceMap());
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
-            it('should returns object with empty obfuscated code and source map with empty data if source code is empty', () => {
-                let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/empty-input.js'),
-                    {
-                        sourceMap: true
-                    }
+            it('should return an empty obfuscated code', () => {
+                assert.isNotOk(obfuscatedCode);
+            });
+        });
+
+        describe('empty source code with comments', () => {
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/comments-only.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
                 );
 
-                assert.isNotOk(obfuscationResult.getObfuscatedCode());
-                assert.deepEqual(JSON.parse(obfuscationResult.getSourceMap()).names, []);
-                assert.deepEqual(JSON.parse(obfuscationResult.getSourceMap()).sources, []);
-                assert.isNotOk(JSON.parse(obfuscationResult.getSourceMap()).mappings);
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
-        });
 
-        it('should returns an empty string if source code is empty', () => {
-            assert.isNotOk(
-                JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/empty-input.js'),
-                ).getObfuscatedCode()
-            );
+            it('should return an empty obfuscated code', () => {
+                assert.isNotOk(obfuscatedCode);
+            });
         });
 
-        it('should returns an empty string if source code contains only comments', () => {
-            assert.isNotOk(
-                JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/comments-only.js'),
-                ).getObfuscatedCode()
-            );
+        describe('`sourceMap` option is `true`', () => {
+            describe('`sourceMapMode` is `separate`', () => {
+                let obfuscatedCode: string,
+                    sourceMap: string;
+
+                beforeEach(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_CUSTOM_NODES_PRESET,
+                            sourceMap: true
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                    sourceMap = JSON.parse(obfuscationResult.getSourceMap()).mappings;
+                });
+
+                it('should return correct obfuscated code', () => {
+                    assert.isOk(obfuscatedCode);
+                });
+
+                it('should return correct source map', () => {
+                    assert.isOk(sourceMap);
+                });
+            });
+
+            describe('`sourceMapMode` is `inline`', () => {
+                const regExp: RegExp = /sourceMappingURL=data:application\/json;base64/;
+
+                let obfuscatedCode: string,
+                    sourceMap: string;
+
+                beforeEach(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_CUSTOM_NODES_PRESET,
+                            sourceMap: true,
+                            sourceMapMode: 'inline'
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                    sourceMap = JSON.parse(obfuscationResult.getSourceMap()).mappings;
+                });
+
+                it('should return correct obfuscated code', () => {
+                    assert.isOk(obfuscatedCode);
+                });
+
+                it('should return obfuscated code with inline source map as Base64 string', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+
+                it('should return correct source map', () => {
+                    assert.isOk(sourceMap);
+                });
+            });
+
+            describe('empty source code', () => {
+                let obfuscatedCode: string,
+                    sourceMapNames: string[],
+                    sourceMapSources: string[],
+                    sourceMapMappings: string;
+
+                beforeEach(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            sourceMap: true
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+                    const sourceMapObject: any = JSON.parse(obfuscationResult.getSourceMap());
+
+                    sourceMapNames = sourceMapObject.names;
+                    sourceMapSources = sourceMapObject.sources;
+                    sourceMapMappings = sourceMapObject.mappings;
+                });
+
+                it('should return empty obfuscated code', () => {
+                    assert.isNotOk(obfuscatedCode);
+                });
+
+                it('should return empty source map property `names`', () => {
+                    assert.deepEqual(sourceMapNames, []);
+                });
+
+                it('should return empty source map property `sources`', () => {
+                    assert.deepEqual(sourceMapSources, []);
+                });
+
+                it('should return empty source map property `mappings`', () => {
+                    assert.isNotOk(sourceMapMappings);
+                });
+            });
         });
 
-        it('should obfuscate simple code with variable inside global scope', () => {
-            assert.match(
-                JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/simple-input-1.js'),
+        describe('variable inside global scope', () => {
+            const regExp: RegExp = /^var *test *= *0x\d+;$/;
+
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
                     {
                         ...NO_CUSTOM_NODES_PRESET
                     }
-                ).getObfuscatedCode(),
-                /^var *test *= *0x\d+;$/
-            );
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should return correct obfuscated code', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
 
-        it('should obfuscate simple code with variable inside block-scope', () => {
-            assert.match(
-                JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/block-scope.js'),
+        describe('variable inside global scope', () => {
+            const regExp: RegExp = /^\(function *\(\) *\{ *var *_0x[\w]+ *= *0x\d+; *\}(\(\)\)|\)\(\));?$/;
+
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/block-scope.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
                     {
                         ...NO_CUSTOM_NODES_PRESET
                     }
-                ).getObfuscatedCode(),
-                /^\(function *\(\) *\{ *var *_0x[\w]+ *= *0x\d+; *\}(\(\)\)|\)\(\));?$/
-            );
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should return correct obfuscated code', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
 
-        it('should obfuscate simple code with latin literal variable value', () => {
-            let stringArrayLatinRegExp: RegExp = /^var _0x(\w){4} *= *\['abc'\];/,
-                stringArrayCallRegExp: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/,
-                obfuscatedCode1: string = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/simple-input-2.js'),
+        describe('latin literal variable value', () => {
+            const stringArrayLatinRegExp: RegExp = /^var _0x(\w){4} *= *\['abc'\];/;
+            const stringArrayCallRegExp: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/;
+
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
                     {
                         ...NO_CUSTOM_NODES_PRESET,
                         stringArray: true,
                         stringArrayThreshold: 1
                     }
-                ).getObfuscatedCode();
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
 
-            assert.match(obfuscatedCode1, stringArrayLatinRegExp);
-            assert.match(obfuscatedCode1, stringArrayCallRegExp);
+            it('match #1: should return correct obfuscated code', () => {
+                assert.match(obfuscatedCode, stringArrayLatinRegExp);
+            });
+
+            it('match #2: should return correct obfuscated code', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
         });
 
-        it('should obfuscate simple code with cyrillic literal variable value', () => {
-            let stringArrayCyrillicRegExp: RegExp = /^var _0x(\w){4} *= *\['(\\u\d+)+'\];/,
-                stringArrayCallRegExp: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/,
-                obfuscatedCode2: string = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js'),
+        describe('cyrillic literal variable value', () => {
+            const stringArrayCyrillicRegExp: RegExp = /^var _0x(\w){4} *= *\['(\\u\d+)+'\];/;
+            const stringArrayCallRegExp: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/;
+
+            let obfuscatedCode: string;
+
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
                     {
                         ...NO_CUSTOM_NODES_PRESET,
                         stringArray: true,
                         stringArrayThreshold: 1
                     }
-                ).getObfuscatedCode();
+                );
 
-            assert.match(obfuscatedCode2, stringArrayCyrillicRegExp);
-            assert.match(obfuscatedCode2, stringArrayCallRegExp);
-        });
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
 
-        it('should returns same code every time with same `seed`', function () {
-            this.timeout(60000);
+            it('match #1: should return correct obfuscated code', () => {
+                assert.match(obfuscatedCode, stringArrayCyrillicRegExp);
+            });
 
-            const code: string = readFileAsString('./test/fixtures/sample.js');
-            const samples: number = 100;
+            it('match #2: should return correct obfuscated code', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
+        });
 
-            let seed: number = 12345,
-                equalsCount: number = 0;
+        describe('seed', function () {
+            this.timeout(60000);
 
-            for (let i: number = 0; i < samples; i++) {
-                if (i % 20 === 0) {
-                    seed++;
-                }
+            describe('same seed on each run', () => {
+                const code: string = readFileAsString('./test/fixtures/sample.js');
+                const samples: number = 100;
+
+                let obfuscatedCode1: string,
+                    obfuscatedCode2: string,
+                    seed: number = 12345,
+                    equalsCount: number = 0;
+
+                beforeEach(() => {
+                    for (let i: number = 0; i < samples; i++) {
+                        if (i % 20 === 0) {
+                            seed++;
+                        }
+
+                        const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                seed: seed
+                            }
+                        );
+                        const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                seed: seed
+                            }
+                        );
+
+                        obfuscatedCode1 = obfuscationResult1.getObfuscatedCode();
+                        obfuscatedCode2 = obfuscationResult2.getObfuscatedCode();
+
+                        if (obfuscatedCode1 === obfuscatedCode2) {
+                            equalsCount++;
+                        }
+                    }
+                });
 
-                const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    code, { seed: seed }
-                );
-                const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    code, { seed: seed }
-                );
+                it('should return same code every time with same `seed`', () => {
+                    assert.equal(equalsCount, samples);
+                });
+            });
 
-                if (obfuscationResult1.getObfuscatedCode() === obfuscationResult2.getObfuscatedCode()) {
-                    equalsCount++;
-                }
-            }
+            describe('variant #1: different seed on each run', () => {
+                const code: string = readFileAsString('./test/fixtures/sample.js');
+
+                let obfuscatedCode1: string,
+                    obfuscatedCode2: string;
+
+                beforeEach(() => {
+                    const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            seed: 12345
+                        }
+                    );
+                    const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            seed: 12346
+                        }
+                    );
+
+                    obfuscatedCode1 = obfuscationResult1.getObfuscatedCode();
+                    obfuscatedCode2 = obfuscationResult2.getObfuscatedCode();
+                });
+
+                it('should return different obfuscated code with different `seed` option value', () => {
+                    assert.notEqual(obfuscatedCode1, obfuscatedCode2);
+                });
+            });
 
-            assert.equal(equalsCount, samples);
+            describe('variant #2: different seed on each run', () => {
+                const code: string = readFileAsString('./test/fixtures/sample.js');
+
+                let obfuscatedCode1: string,
+                    obfuscatedCode2: string;
+
+                beforeEach(() => {
+                    const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            seed: 0
+                        }
+                    );
+                    const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            seed: 0
+                        }
+                    );
+
+                    obfuscatedCode1 = obfuscationResult1.getObfuscatedCode();
+                    obfuscatedCode2 = obfuscationResult2.getObfuscatedCode();
+                });
+
+                it('should return different obfuscated code with different `seed` option value', () => {
+                    assert.notEqual(obfuscatedCode1, obfuscatedCode2);
+                });
+            });
         });
 
-        it('should returns different code with different `seed` option value', () => {
-            const code: string = readFileAsString('./test/fixtures/sample.js');
-
-            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                code, { seed: 12345 }
-            );
-            const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                code, { seed: 12346 }
-            );
-
-            const obfuscationResult3: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                code, { seed: 0 }
-            );
-            const obfuscationResult4: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                code, { seed: 0 }
-            );
-
-            assert.notEqual(obfuscationResult1.getObfuscatedCode(), obfuscationResult2.getObfuscatedCode());
-            assert.notEqual(obfuscationResult3.getObfuscatedCode(), obfuscationResult4.getObfuscatedCode());
-        });
+        describe('new.target MetaProperty', () => {
+            const regExp: RegExp = /new\.target *=== *Foo/;
+
+            let obfuscatedCode: string;
 
-        it('should keep new.target MetaProperty', () => {
-            assert.match(
-                JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/new-target.js'),
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/new-target.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
                     {
                         ...NO_CUSTOM_NODES_PRESET
                     }
-                ).getObfuscatedCode(),
-                /new\.target *=== *Foo/
-            );
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should keep new.target MetaProperty', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
 
-        it('should mangle obfuscated code', () => {
-            const code: string = readFileAsString(__dirname + '/fixtures/mangle.js');
+        describe('mangle', () => {
+            const regExp: RegExp = /var *a *= *0x1/;
 
-            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                code, { mangle: true }
-            );
-            const mangleMatch: RegExp = /var *a *= *0x1/;
+            let obfuscatedCode: string;
 
-            assert.match(obfuscationResult1.getObfuscatedCode(), mangleMatch);
+            beforeEach(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/mangle.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        mangle: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should mangle obfuscated code', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
 
         afterEach(() => {

+ 325 - 170
test/functional-tests/node-transformers/control-flow-transformers/block-statement-control-flow-transformer/BlockStatementControlFlowTransformer.spec.ts

@@ -8,229 +8,384 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
-describe('BlockStatementControlFlowTransformer', () => {
-    describe('transformNode (blockStatementNode: ESTree.BlockStatement): ESTree.Node', () => {
-        const switchCaseMapStringRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *\{'.*' *: *'(.*)'\};/;
-        const switchCaseMapVariableRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4,6}\['.*'\]\['split'\]\('\\x7c'\)/;
+/**
+ * @param hexNumber
+ * @return {RegExp}
+ */
+const getStatementRegExp: (hexNumber: string) => RegExp = (hexNumber) => {
+    return new RegExp(`console\\['log'\\]\\(${hexNumber}\\);`);
+};
+
+describe('BlockStatementControlFlowTransformer', function () {
+    this.timeout(100000);
 
+    describe('transformNode (blockStatementNode: ESTree.BlockStatement): ESTree.Node', () => {
         describe('variant #1: 5 simple statements', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-1.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-            const statementRegExp1: RegExp = /console\['log'\]\(0x1\);/;
-            const statementRegExp2: RegExp = /console\['log'\]\(0x2\);/;
-            const statementRegExp3: RegExp = /console\['log'\]\(0x3\);/;
-            const statementRegExp4: RegExp = /console\['log'\]\(0x4\);/;
-            const statementRegExp5: RegExp = /console\['log'\]\(0x5\);/;
-
-            const switchCaseRegExp: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/;
-            const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
-            const switchCaseLength: number = obfuscatedCode.match(switchCaseLengthRegExp)!.length;
-
-            const switchCaseMapStringMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(switchCaseMapStringRegExp);
-            const switchCaseMapMatch: string = switchCaseMapStringMatches[1];
-            const switchCaseMap: string[] = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort();
-
-            it('should save all statements', () => {
-                assert.match(obfuscatedCode, statementRegExp1);
-                assert.match(obfuscatedCode, statementRegExp2);
-                assert.match(obfuscatedCode, statementRegExp3);
-                assert.match(obfuscatedCode, statementRegExp4);
-                assert.match(obfuscatedCode, statementRegExp5);
-            });
-
-            it('should wrap block statement statements in switch case structure', () => {
-                assert.match(obfuscatedCode, switchCaseRegExp);
-                assert.equal(switchCaseLength, 5);
-            });
-
-            it('should create variable with order of switch cases sequence', () => {
-                assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']);
-                assert.match(obfuscatedCode, switchCaseMapVariableRegExp);
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            describe('`console.log` statements', ()=> {
+                const statementRegExp1: RegExp = getStatementRegExp('0x1');
+                const statementRegExp2: RegExp = getStatementRegExp('0x2');
+                const statementRegExp3: RegExp = getStatementRegExp('0x3');
+                const statementRegExp4: RegExp = getStatementRegExp('0x4');
+                const statementRegExp5: RegExp = getStatementRegExp('0x5');
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp1);
+                });
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp2);
+                });
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp3);
+                });
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp4);
+                });
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp5);
+                });
+            });
+
+            describe('block statement statements', () => {
+                const switchCaseRegExp: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/;
+                const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
+                const expectedSwitchCaseLength: number = 5;
+
+                let switchCaseLength: number;
+
+                before(() => {
+                    switchCaseLength = obfuscatedCode.match(switchCaseLengthRegExp)!.length;
+                });
+
+                it('should wrap block statement statements in switch-case structure', () => {
+                    assert.match(obfuscatedCode, switchCaseRegExp);
+                });
+
+                it('each statement should be wrapped by switch-case structure', () => {
+                    assert.equal(switchCaseLength, expectedSwitchCaseLength);
+                });
+            });
+
+            describe('switch-case map', () => {
+                const switchCaseMapVariableRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4,6}\['.*'\]\['split'\]\('\\x7c'\)/;
+                const expectedSwitchCasesSequence: string[] = ['0', '1', '2', '3', '4'];
+
+                let switchCaseMap: string[];
+
+                before(() => {
+                    const switchCaseMapStringRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *\{'.*' *: *'(.*)'\};/;
+                    const switchCaseMapStringMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(switchCaseMapStringRegExp);
+                    const switchCaseMapMatch: string = switchCaseMapStringMatches[1];
+
+                    switchCaseMap = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort();
+                });
+
+                it('should create switch-case map variable', () => {
+                    assert.match(obfuscatedCode, switchCaseMapVariableRegExp);
+                });
+
+                it('should create valid switch-case map variable with order of switch cases sequence', () => {
+                    assert.deepEqual(switchCaseMap, expectedSwitchCasesSequence);
+                });
             });
         });
 
         describe('variant #2: 5 simple statements inside while loop without break or continue statement', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-2.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1,
-                    unicodeEscapeSequence: false
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-            const statementRegExp1: RegExp = /console\['log'\]\(0x1\);/;
-            const statementRegExp2: RegExp = /console\['log'\]\(0x2\);/;
-            const statementRegExp3: RegExp = /console\['log'\]\(0x3\);/;
-            const statementRegExp4: RegExp = /console\['log'\]\(0x4\);/;
-            const statementRegExp5: RegExp = /console\['log'\]\(0x5\);/;
-
-            const switchCaseRegExp: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/;
-            const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
-            const switchCaseLength: number = obfuscatedCode.match(switchCaseLengthRegExp)!.length;
-
-            const switchCaseMapStringMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(switchCaseMapStringRegExp);
-            const switchCaseMapMatch: string = switchCaseMapStringMatches[1];
-            const switchCaseMap: string[] = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort();
-
-            it('should save all statements', () => {
-                assert.match(obfuscatedCode, statementRegExp1);
-                assert.match(obfuscatedCode, statementRegExp2);
-                assert.match(obfuscatedCode, statementRegExp3);
-                assert.match(obfuscatedCode, statementRegExp4);
-                assert.match(obfuscatedCode, statementRegExp5);
-            });
-
-            it('should wrap block statement statements in switch case structure', () => {
-                assert.match(obfuscatedCode, switchCaseRegExp);
-                assert.equal(switchCaseLength, 5);
-            });
-
-            it('should create variable with order of switch cases sequence', () => {
-                assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']);
-                assert.match(obfuscatedCode, switchCaseMapVariableRegExp);
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-2.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1,
+                        unicodeEscapeSequence: false
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
-        });
 
-        describe('variant #3: less then 5 statements', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/one-statement.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+            describe('`console.log` statements', ()=> {
+                const statementRegExp1: RegExp = getStatementRegExp('0x1');
+                const statementRegExp2: RegExp = getStatementRegExp('0x2');
+                const statementRegExp3: RegExp = getStatementRegExp('0x3');
+                const statementRegExp4: RegExp = getStatementRegExp('0x4');
+                const statementRegExp5: RegExp = getStatementRegExp('0x5');
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp1);
+                });
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp2);
+                });
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp3);
+                });
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp4);
+                });
+
+                it('should save statement', () => {
+                    assert.match(obfuscatedCode, statementRegExp5);
+                });
+            });
+
+            describe('block statement statements', () => {
+                const switchCaseRegExp: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/;
+                const expectedSwitchCaseLength: number = 5;
+
+                let switchCaseLength: number;
+
+                before(() => {
+                    const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g;
+
+                    switchCaseLength = obfuscatedCode.match(switchCaseLengthRegExp)!.length;
+                });
+
+                it('should wrap block statement statements in switch-case structure', () => {
+                    assert.match(obfuscatedCode, switchCaseRegExp);
+                });
+
+                it('each statement should be wrapped by switch-case structure', () => {
+                    assert.equal(switchCaseLength, expectedSwitchCaseLength);
+                });
+            });
+
+            describe('switch-case map', () => {
+                const switchCaseMapVariableRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4,6}\['.*'\]\['split'\]\('\\x7c'\)/;
+                const expectedSwitchCasesSequence: string[] = ['0', '1', '2', '3', '4'];
+
+                let switchCaseMap: string[];
+
+                before(() => {
+                    const switchCaseMapStringRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *\{'.*' *: *'(.*)'\};/;
+                    const switchCaseMapStringMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(switchCaseMapStringRegExp);
+                    const switchCaseMapMatch: string = switchCaseMapStringMatches[1];
 
+                    switchCaseMap = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort();
+                });
+
+                it('should create switch-case map variable', () => {
+                    assert.match(obfuscatedCode, switchCaseMapVariableRegExp);
+                });
+
+                it('should create valid switch-case map variable with order of switch cases sequence', () => {
+                    assert.deepEqual(switchCaseMap, expectedSwitchCasesSequence);
+                });
+            });
+        });
+
+        describe('variant #3: statements length less then 5 statements', () => {
             const statementRegExp: RegExp = /^\(function *\( *\) *\{ *console\['log'\]\(0x1\); *\} *\( *\) *\);$/;
 
-            it('shouldn\'t transform block statement if statements length less than 5', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/one-statement.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t transform block statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
             });
         });
 
-        describe('variant #4: const declaration inside block statement', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/const-declaration.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
+        describe('variant #4: block statement contain variable declaration with `const` kind', () => {
             const statementRegExp: RegExp = /^\(function *\( *\) *\{ *const *_0x([a-f0-9]){4,6} *= *0x1; *console\['log'\]\(0x1\);/;
 
-            it('shouldn\'t transform block statement if block statement contain variable declaration with `const` kind', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/const-declaration.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t transform block statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
             });
         });
 
-        describe('variant #5: let declaration inside block statement', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/let-declaration.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
+        describe('variant #5: block statement contain variable declaration with `let` kind', () => {
             const statementRegExp: RegExp = /^\(function *\( *\) *\{ *let *_0x([a-f0-9]){4,6} *= *0x1; *console\['log'\]\(0x1\);/;
 
-            it('shouldn\'t transform block statement if block statement contain variable declaration with `let` kind', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/let-declaration.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t transform block statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
             });
         });
 
-        describe('variant #6: break statement inside block statement', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/break-statement.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
+        describe('variant #6: block statement contain break statement', () => {
             const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *break; *console\['log'\]\(0x1\);/;
 
-            it('shouldn\'t transform block statement if block statement contain break statement', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/break-statement.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t transform block statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
             });
         });
 
-        describe('variant #7: continue statement inside block statement', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/continue-statement.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
+        describe('variant #7: block statement contain continue statement', () => {
             const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *continue; *console\['log'\]\(0x1\);/;
 
-            it('shouldn\'t transform block statement if block statement contain continue statement', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/continue-statement.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t transform block statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
             });
         });
 
-        describe('variant #8: function declaration inside block statement', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/function-declaration.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+        describe('variant #8: block statement contain function declaration', () => {
+            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *function *_0x([a-f0-9]){4,6} *\( *\) *\{ *\} *console\['log'\]\(0x1\);/
+
+            let obfuscatedCode: string;
 
-            const statementRegExp: RegExp = /^\(function *\( *\) *\{ *function *_0x([a-f0-9]){4,6} *\( *\) *\{ *\} *console\['log'\]\(0x1\);/;
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/function-declaration.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
 
-            it('shouldn\'t transform block statement if block statement contain function declaration', () => {
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t transform block statement', () => {
                 assert.match(obfuscatedCode, statementRegExp);
             });
         });
 
         describe('variant #9: `controlFlowFlatteningThreshold` chance', () => {
             const samples: number = 1000;
-            const controlFlowFlatteningThreshold: number = 0.5;
             const delta: number = 0.1;
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-1.js').repeat(samples),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: controlFlowFlatteningThreshold,
-                }
-            );
+
+            const controlFlowFlatteningThreshold: number = 0.5;
 
             const regExp1: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/g;
             const regExp2: RegExp = /\(function *\( *\) *\{ *console\['log'\]\(0x1\);/g;
-            const transformedStatementMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp1)!.length;
-            const untouchedStatementMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp2)!.length;
+
+            let transformedStatementPercentage: number,
+                untouchedStatementPercentage: number;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code.repeat(samples),
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: controlFlowFlatteningThreshold,
+                    }
+                );
+
+                const transformedStatementMatchesLength: number = obfuscationResult
+                    .getObfuscatedCode()
+                    .match(regExp1)!
+                    .length;
+                const untouchedStatementMatchesLength: number = obfuscationResult
+                    .getObfuscatedCode()
+                    .match(regExp2)!
+                    .length;
+
+                transformedStatementPercentage = transformedStatementMatchesLength / samples;
+                untouchedStatementPercentage = untouchedStatementMatchesLength / samples;
+            });
 
             it('should transform block statement with `controlFlowFlatteningThreshold` chance', () => {
-                assert.closeTo(transformedStatementMatchesLength / samples, controlFlowFlatteningThreshold, delta);
-                assert.closeTo(untouchedStatementMatchesLength / samples, controlFlowFlatteningThreshold, delta);
+                assert.closeTo(transformedStatementPercentage, controlFlowFlatteningThreshold, delta);
+            });
+
+            it('should keep block statement with (1 - `controlFlowFlatteningThreshold`) chance', () => {
+                assert.closeTo(untouchedStatementPercentage, controlFlowFlatteningThreshold, delta);
             });
         });
     });

+ 62 - 29
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts

@@ -8,63 +8,96 @@ import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator';
 
-describe('BinaryExpressionControlFlowReplacer', () => {
+describe('BinaryExpressionControlFlowReplacer', function () {
+    this.timeout(100000);
+
     describe('replace (binaryExpressionNode: ESTree.BinaryExpression,parentNode: ESTree.Node,controlFlowStorage: IStorage <ICustomNode>)', () => {
         describe('variant #1 - single binary expression', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-1.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\]\(0x1, *0x2\);/;
 
-            it('should replace binary expression node by call to control flow storage node', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should replace binary expression node with call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
             });
         });
 
         describe('variant #2 - multiple binary expressions with threshold = 1', () => {
-            it('should replace binary expression node by call to control flow storage node', function () {
-                this.timeout(60000);
+            const expectedMatchErrorsCount: number = 0;
+            const expectedChance: number = 0.5;
+
+            const samplesCount: number = 1000;
+            const delta: number = 0.1;
+
+            const controlFlowStorageCallRegExp1: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(0x1, *0x2\);/;
+            const controlFlowStorageCallRegExp2: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(0x2, *0x3\);/;
 
-                const samplesCount: number = 1000;
-                const expectedValue: number = 0.5;
-                const delta: number = 0.1;
+            let matchErrorsCount: number = 0,
+                usingExistingIdentifierChance: number;
 
-                let equalsValue: number = 0;
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-2.js');
+
+                let obfuscationResult: IObfuscationResult,
+                    obfuscatedCode: string,
+                    firstMatchArray: RegExpMatchArray | null,
+                    secondMatchArray: RegExpMatchArray | null,
+                    firstMatch: string | undefined,
+                    secondMatch: string | undefined,
+                    equalsValue: number = 0;
 
                 for (let i = 0; i < samplesCount; i++) {
-                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                        readFileAsString(__dirname + '/fixtures/input-2.js'),
+                    obfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
                         {
                             ...NO_CUSTOM_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }
                     );
-                    const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(0x1, *0x2\);/;
-                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(0x2, *0x3\);/;
 
-                    const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
-                    const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+                    firstMatchArray = obfuscatedCode.match(controlFlowStorageCallRegExp1);
+                    secondMatchArray = obfuscatedCode.match(controlFlowStorageCallRegExp2);
 
-                    const firstMatch: string | undefined = firstMatchArray ? firstMatchArray[2] : undefined;
-                    const secondMatch: string | undefined = secondMatchArray ? secondMatchArray[2] : undefined;
+                    if (!firstMatchArray || !secondMatchArray) {
+                        matchErrorsCount++;
+
+                        continue;
+                    }
 
-                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp1);
-                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
+                    firstMatch = firstMatchArray ? firstMatchArray[1] : undefined;
+                    secondMatch = secondMatchArray ? secondMatchArray[1] : undefined;
 
                     if (firstMatch === secondMatch) {
                         equalsValue++;
                     }
                 }
 
-                assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
+                usingExistingIdentifierChance = equalsValue / samplesCount;
+            });
+
+            it('should replace binary expression node with call to control flow storage node', () => {
+                assert.equal(matchErrorsCount, expectedMatchErrorsCount);
+            });
+
+            it('should use existing identifier for control flow storage with expected chance', () => {
+                assert.closeTo(usingExistingIdentifierChance, expectedChance, delta);
             });
         });
     });

+ 82 - 42
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts

@@ -8,80 +8,120 @@ import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator';
 
-describe('CallExpressionControlFlowReplacer', () => {
+describe('CallExpressionControlFlowReplacer', function () {
+    this.timeout(100000);
+
     describe('replace (callExpressionNode: ESTree.CallExpression,parentNode: ESTree.Node,controlFlowStorage: IStorage <ICustomNode>)', () => {
         describe('variant #1 - single call expression', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-1.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\]\(_0x([a-f0-9]){4,6}, *0x1, *0x2\);/;
 
-            it('should replace call expression node by call to control flow storage node', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should replace call expression node with call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
             });
         });
 
         describe('variant #2 - multiple call expressions with threshold = 1', () => {
-            it('should replace call expression node by call to control flow storage node', function () {
-                this.timeout(60000);
+            const expectedMatchErrorsCount: number = 0;
+            const expectedChance: number = 0.5;
+
+            const samplesCount: number = 1000;
+            const delta: number = 0.1;
+
+            const controlFlowStorageCallRegExp1: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(_0x([a-f0-9]){4,6}, *0x1, *0x2\);/;
+            const controlFlowStorageCallRegExp2: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(_0x([a-f0-9]){4,6}, *0x2, *0x3\);/;
 
-                const samplesCount: number = 1000;
-                const expectedValue: number = 0.5;
-                const delta: number = 0.1;
+            let matchErrorsCount: number = 0,
+                usingExistingIdentifierChance: number;
 
-                let equalsValue: number = 0;
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-2.js');
+
+                let obfuscationResult: IObfuscationResult,
+                    obfuscatedCode: string,
+                    firstMatchArray: RegExpMatchArray | null,
+                    secondMatchArray: RegExpMatchArray | null,
+                    firstMatch: string | undefined,
+                    secondMatch: string | undefined,
+                    equalsValue: number = 0;
 
                 for (let i = 0; i < samplesCount; i++) {
-                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                        readFileAsString(__dirname + '/fixtures/input-2.js'),
+                    obfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
                         {
                             ...NO_CUSTOM_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }
                     );
-                    const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(_0x([a-f0-9]){4,6}, *0x1, *0x2\);/;
-                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(_0x([a-f0-9]){4,6}, *0x2, *0x3\);/;
 
-                    const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
-                    const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+                    firstMatchArray = obfuscatedCode.match(controlFlowStorageCallRegExp1);
+                    secondMatchArray = obfuscatedCode.match(controlFlowStorageCallRegExp2);
+
+                    if (!firstMatchArray || !secondMatchArray) {
+                        matchErrorsCount++;
 
-                    const firstMatch: string | undefined = firstMatchArray ? firstMatchArray[2] : undefined;
-                    const secondMatch: string | undefined = secondMatchArray ? secondMatchArray[2] : undefined;
+                        continue;
+                    }
 
-                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp1);
-                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
+                    firstMatch = firstMatchArray ? firstMatchArray[1] : undefined;
+                    secondMatch = secondMatchArray ? secondMatchArray[1] : undefined;
 
                     if (firstMatch === secondMatch) {
                         equalsValue++;
                     }
                 }
 
-                assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
+                usingExistingIdentifierChance = equalsValue / samplesCount;
+            });
+
+            it('should replace call expression node by call to control flow storage node', () => {
+                assert.equal(matchErrorsCount, expectedMatchErrorsCount);
+            });
+
+            it('should use existing identifier for control flow storage with expected chance', () => {
+                assert.closeTo(usingExistingIdentifierChance, expectedChance, delta);
             });
         });
 
-        describe('variant #3 - callee - member expression', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-3.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['sum'\]\(0x1, *0x2\);/;
+        describe('variant #3 - call expression callee is member expression node', () => {
+            const regExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['sum'\]\(0x1, *0x2\);/;
 
-            it('shouldn\'t replace call expression node by call to control flow storage node if call expression callee is member expression node', () => {
-                assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-3.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t replace call expression node with call to control flow storage node', () => {
+                assert.match(obfuscatedCode, regExp);
             });
         });
     });

+ 95 - 48
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts

@@ -8,95 +8,142 @@ import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator';
 
-describe('LogicalExpressionControlFlowReplacer', () => {
+describe('LogicalExpressionControlFlowReplacer', function () {
+    this.timeout(100000);
+
     describe('replace (logicalExpressionNode: ESTree.LogicalExpression,parentNode: ESTree.Node,controlFlowStorage: IStorage <ICustomNode>)', () => {
         describe('variant #1 - single logical expression', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-1.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\]\(!!\[\], *!\[\]\);/;
 
-            it('should replace logical expression node by call to control flow storage node', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should replace logical expression node with call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
             });
         });
 
         describe('variant #2 - multiple logical expressions with threshold = 1', () => {
-            it('should replace logical expression node by call to control flow storage node', function () {
-                this.timeout(60000);
+            const expectedMatchErrorsCount: number = 0;
+            const expectedChance: number = 0.5;
 
-                const samplesCount: number = 1000;
-                const expectedValue: number = 0.5;
-                const delta: number = 0.1;
+            const samplesCount: number = 1000;
+            const delta: number = 0.1;
 
-                let equalsValue: number = 0;
+            const controlFlowStorageCallRegExp1: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(!!\[\], *!\[\]\);/;
+            const controlFlowStorageCallRegExp2: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(!\[\], *!!\[\]\);/;
+
+            let matchErrorsCount: number = 0,
+                usingExistingIdentifierChance: number;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-2.js');
+
+                let obfuscationResult: IObfuscationResult,
+                    obfuscatedCode: string,
+                    firstMatchArray: RegExpMatchArray | null,
+                    secondMatchArray: RegExpMatchArray | null,
+                    firstMatch: string | undefined,
+                    secondMatch: string | undefined,
+                    equalsValue: number = 0;
 
                 for (let i = 0; i < samplesCount; i++) {
-                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                        readFileAsString(__dirname + '/fixtures/input-2.js'),
+                    obfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
                         {
                             ...NO_CUSTOM_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }
                     );
-                    const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(!!\[\], *!\[\]\);/;
-                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-f0-9]){4,6} *= *(_0x([a-f0-9]){4,6}\['\w{3}'\])\(!\[\], *!!\[\]\);/;
 
-                    const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
-                    const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+                    firstMatchArray = obfuscatedCode.match(controlFlowStorageCallRegExp1);
+                    secondMatchArray = obfuscatedCode.match(controlFlowStorageCallRegExp2);
 
-                    const firstMatch: string | undefined = firstMatchArray ? firstMatchArray[2] : undefined;
-                    const secondMatch: string | undefined = secondMatchArray ? secondMatchArray[2] : undefined;
+                    if (!firstMatchArray || !secondMatchArray) {
+                        matchErrorsCount++;
 
-                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp1);
-                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
+                        continue;
+                    }
+
+                    firstMatch = firstMatchArray ? firstMatchArray[1] : undefined;
+                    secondMatch = secondMatchArray ? secondMatchArray[1] : undefined;
 
                     if (firstMatch === secondMatch) {
                         equalsValue++;
                     }
                 }
 
-                assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
+                usingExistingIdentifierChance = equalsValue / samplesCount;
+            });
+
+            it('should replace logical expression node by call to control flow storage node', () => {
+                assert.equal(matchErrorsCount, expectedMatchErrorsCount);
+            });
+
+            it('should use existing identifier for control flow storage with expected chance', () => {
+                assert.closeTo(usingExistingIdentifierChance, expectedChance, delta);
             });
         });
 
         describe('variant #3 - single logical expression with unary expression', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-3.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\]\(!_0x([a-f0-9]){4,6}, *!_0x([a-f0-9]){4,6}\);/;
 
-            it('should replace logical expression node with unary expression by call to control flow storage node', () => {
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-3.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should replace logical unary expression with call to control flow storage node', () => {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
             });
         });
 
         describe('prohibited nodes variant #1', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/prohibited-nodes.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: .1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const regExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\] *&& *!\[\];/;
 
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/prohibited-nodes.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: .1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
             it('shouldn\'t replace prohibited expression nodes', () => {
                 assert.match(obfuscatedCode, regExp);
             });

+ 17 - 10
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec.ts

@@ -10,23 +10,30 @@ import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator
 
 describe('StringLiteralControlFlowReplacer', () => {
     describe('replace (literalNode: ESTree.Literal,parentNode: ESTree.Node,controlFlowStorage: IStorage <ICustomNode>)', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/input-1.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                controlFlowFlattening: true,
-                controlFlowFlatteningThreshold: 1
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
         const controlFlowStorageStringLiteralRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *\{'\w{3}' *: *'test'\};/;
         const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['\w{3}'\];/;
 
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    controlFlowFlattening: true,
+                    controlFlowFlatteningThreshold: 1
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
         it('should add string literal node as property of control flow storage node', () => {
             assert.match(obfuscatedCode, controlFlowStorageStringLiteralRegExp);
         });
 
-        it('should replace string literal node by call to control flow storage node', () => {
+        it('should replace string literal node with call to control flow storage node', () => {
             assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
         });
     });

+ 148 - 84
test/functional-tests/node-transformers/control-flow-transformers/function-control-flow-transformer/FunctionControlFlowTransformer.spec.ts

@@ -8,7 +8,9 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
-describe('FunctionControlFlowTransformer', () => {
+describe('FunctionControlFlowTransformer', function () {
+    this.timeout(100000);
+
     const variableMatch: string = '_0x([a-f0-9]){4,6}';
     const rootControlFlowStorageNodeMatch: string = `` +
         `var *${variableMatch} *= *\\{` +
@@ -27,26 +29,35 @@ describe('FunctionControlFlowTransformer', () => {
 
     describe('transformNode (functionNode: ESTree.Function): ESTree.Node', () => {
         describe('variant #1 - single `control flow storage` node with single item', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-1.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const regexp: RegExp = new RegExp(rootControlFlowStorageNodeMatch);
 
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
             it('should add `control flow storage` node to the obfuscated code', () => {
                 assert.match(obfuscatedCode, regexp);
             });
         });
 
         describe('variant #2 - two `control flow storage` nodes: root and inner', () => {
+            const expectedAppendToScopeThreshold: number = 0.5;
+
             const samplesCount: number = 1000;
             const delta: number = 0.1;
-            const expectedValue: number = 0.5;
+
             const regExp1: RegExp = new RegExp(
                 `\\(function\\(\\) *\\{ *${rootControlFlowStorageNodeMatch}`,
                 'g'
@@ -56,43 +67,44 @@ describe('FunctionControlFlowTransformer', () => {
                 'g'
             );
 
-            let totalValue: number = 0;
+            let appendToScopeThreshold: number = 0;
 
-            for (let i = 0; i < samplesCount; i++) {
-                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/input-2.js'),
-                    {
-                        ...NO_CUSTOM_NODES_PRESET,
-                        controlFlowFlattening: true,
-                        controlFlowFlatteningThreshold: 1
-                    }
-                );
-                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-2.js');
+
+                let obfuscationResult: IObfuscationResult,
+                    obfuscatedCode: string,
+                    totalValue: number = 0;
+
+                for (let i = 0; i < samplesCount; i++) {
+                    obfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_CUSTOM_NODES_PRESET,
+                            controlFlowFlattening: true,
+                            controlFlowFlatteningThreshold: 1
+                        }
+                    );
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
 
-                if (regExp1.test(obfuscatedCode)) {
-                    totalValue += obfuscatedCode.match(regExp1)!.length;
+                    if (regExp1.test(obfuscatedCode)) {
+                        totalValue += obfuscatedCode.match(regExp1)!.length;
 
-                    if (regExp2.test(obfuscatedCode)) {
-                        totalValue += obfuscatedCode.match(regExp2)!.length;
+                        if (regExp2.test(obfuscatedCode)) {
+                            totalValue += obfuscatedCode.match(regExp2)!.length;
+                        }
                     }
                 }
-            }
+
+                appendToScopeThreshold = (totalValue - samplesCount) / samplesCount;
+            });
 
             it('should add two `control flow storage` nodes (root and inner) to the obfuscated code in different scopes', () => {
-                assert.closeTo((totalValue - samplesCount) / samplesCount, expectedValue, delta);
+                assert.closeTo(appendToScopeThreshold, expectedAppendToScopeThreshold, delta);
             });
         });
 
         describe('variant #3 - single `control flow storage` node with multiple items', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/multiple-items.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
             const regexp: RegExp = new RegExp(
                 `var *${variableMatch} *= *\\{` +
                     `'\\w{3}' *: *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
@@ -104,30 +116,55 @@ describe('FunctionControlFlowTransformer', () => {
                 `\\};`
             );
 
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/multiple-items.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
             it('should add `control flow storage` node with multiple items to the obfuscated code', () => {
                 assert.match(obfuscatedCode, regexp);
             });
         });
 
-        describe('variant #4 - no `control flow storage` node to the root block scope', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/root-block-scope-1.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+        describe('variant #4 - transformed node in the root block scope', () => {
+            const regExp: RegExp = /^var *test *= *0x1 *\+ *0x2;$/;
 
-            it('should\'t add control flow storage node when transformed node in the root block scope', () => {
-                assert.match(obfuscatedCode, /^var *test *= *0x1 *\+ *0x2;$/);
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/root-block-scope-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should\'t add control flow storage node', () => {
+                assert.match(obfuscatedCode, regExp);
             });
         });
 
-        describe('variant #5 - no `control flow storage` node in the root block scope', () => {
-            const samplesCount: number = 20;
+        describe('variant #5 - transformed nodes not in the root block scope', () => {
             const expectedValue: number = 0;
+            const samplesCount: number = 20;
+
             const regExp: RegExp = new RegExp(
                 `var *[a-zA-Z]{6} *= *\\{` +
                     `'\\w{3}' *: *function *_0x[0-9] *\\(${variableMatch}, *${variableMatch}\\) *\\{` +
@@ -136,13 +173,14 @@ describe('FunctionControlFlowTransformer', () => {
                 `\\};`
             );
 
+            const code: string = readFileAsString(__dirname + '/fixtures/root-block-scope-2.js');
 
-            it('should\'t add control flow storage node to the root block scope when transformed nodes not in the root block scope', () => {
-                let totalValue: number = 0;
+            let totalValue: number = 0;
 
+            before(() => {
                 for (let i = 0; i < samplesCount; i++) {
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                        readFileAsString(__dirname + '/fixtures/root-block-scope-2.js'),
+                        code,
                         {
                             ...NO_CUSTOM_NODES_PRESET,
                             controlFlowFlattening: true,
@@ -155,60 +193,86 @@ describe('FunctionControlFlowTransformer', () => {
                         totalValue++;
                     }
                 }
+            });
 
+            it('should\'t add control flow storage node to the root block scope', () => {
                 assert.equal(totalValue, expectedValue);
             });
         });
 
-        describe('variant #6 - no single `control flow storage` node when threshold is 0', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/zero-threshold.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    controlFlowFlattening: true,
-                    controlFlowFlatteningThreshold: 0
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const controlFlowStorageMatch: RegExp = new RegExp(rootControlFlowStorageNodeMatch);
+        describe('variant #6 - threshold is `0`', () => {
             const regexp: RegExp = /var *_0x([a-f0-9]){4,6} *= *0x1 *\+ *0x2;/;
+            const controlFlowStorageRegExp: RegExp = new RegExp(rootControlFlowStorageNodeMatch);
 
-            it('shouldn\'t add `control flow storage` node to the obfuscated code when threshold is 0', () => {
-                assert.match(obfuscatedCode, regexp);
-                assert.notMatch(obfuscatedCode, controlFlowStorageMatch);
-            });
-        });
+            let obfuscatedCode: string;
 
-        describe('arrow function expression', () => {
-            describe('variant #1 - arrow function expression with body', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/zero-threshold.js');
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/arrow-function-expression-with-body.js'),
+                    code,
                     {
                         ...NO_CUSTOM_NODES_PRESET,
                         controlFlowFlattening: true,
-                        controlFlowFlatteningThreshold: 1
+                        controlFlowFlatteningThreshold: 0
                     }
                 );
-                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t add call to control flow storage node to the obfuscated code', () => {
+                assert.match(obfuscatedCode, regexp);
+            });
+
+            it('shouldn\'t add `control flow storage` node to the obfuscated code', () => {
+                assert.notMatch(obfuscatedCode, controlFlowStorageRegExp);
+            });
+        });
+
+        describe('arrow function expression', () => {
+            describe('variant #1 - arrow function expression with body', () => {
                 const regexp: RegExp = new RegExp(rootControlFlowStorageNodeMatch);
 
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/arrow-function-expression-with-body.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_CUSTOM_NODES_PRESET,
+                            controlFlowFlattening: true,
+                            controlFlowFlatteningThreshold: 1
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
                 it('should add `control flow storage` node to the obfuscated code', () => {
                     assert.match(obfuscatedCode, regexp);
                 });
             });
 
             describe('variant #2 - arrow function expression without body', () => {
-                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/arrow-function-expression-without-body.js'),
-                    {
-                        ...NO_CUSTOM_NODES_PRESET,
-                        controlFlowFlattening: true,
-                        controlFlowFlatteningThreshold: 1
-                    }
-                );
-                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
                 const regexp: RegExp = new RegExp(`var *${variableMatch} *= *\\(\\) *=> *0x1 *\\+ *0x2;`);
 
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/arrow-function-expression-without-body.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_CUSTOM_NODES_PRESET,
+                            controlFlowFlattening: true,
+                            controlFlowFlatteningThreshold: 1
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
                 it('shouldn\'t add `control flow storage` node to the obfuscated code', () => {
                     assert.match(obfuscatedCode, regexp);
                 });

+ 94 - 42
test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts

@@ -10,56 +10,108 @@ import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('MemberExpressionTransformer', () => {
     describe('transformation of member expression node with dot notation', () => {
-        it('should replace member expression dot notation call by square brackets call with unicode literal value', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/dot-notation-call.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET
-                }
-            );
-
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *console\['log'\];/);
+        describe('`stringArray` option is disabled', () => {
+            const regExp: RegExp = /var *test *= *console\['log'\];/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/dot-notation-call.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should replace member expression dot notation call with literal value', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
         });
 
-        it('should replace member expression dot notation call by square brackets call to unicode array', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/dot-notation-call.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
-
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['log'\];/);
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *console\[_0x([a-f0-9]){4}\('0x0'\)\];/);
+        describe('`stringArray` option is enabled', () => {
+            const stringArrayRegExp: RegExp = /var *_0x([a-f0-9]){4} *= *\['log'\];/;
+            const stringArrayCallRegExp: RegExp = /var *test *= *console\[_0x([a-f0-9]){4}\('0x0'\)\];/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/dot-notation-call.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should add member expression identifier to string array', () => {
+                assert.match(obfuscatedCode, stringArrayRegExp);
+            });
+
+            it('should replace member expression dot notation call with call to string array', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
         });
     });
 
-    describe('transformation of member expression node without dot notation', () => {
-        it('should replace member expression square brackets call by square brackets call to unicode array', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/square-brackets-call.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
-
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['log'\];/);
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *console\[_0x([a-f0-9]){4}\('0x0'\)\];/);
+    describe('transformation of member expression node with square brackets', () => {
+        describe('variant #1: square brackets literal ', () => {
+            const stringArrayRegExp: RegExp = /var *_0x([a-f0-9]){4} *= *\['log'\];/;
+            const stringArrayCallRegExp: RegExp = /var *test *= *console\[_0x([a-f0-9]){4}\('0x0'\)\];/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/square-brackets-call.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should add member expression square brackets literal to string array', () => {
+                assert.match(obfuscatedCode, stringArrayRegExp);
+            });
+
+            it('should replace member expression square brackets identifier with call to string array', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
         });
 
-        it('should ignore square brackets call with identifier value', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/square-brackets-with-identifier-call.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET
-                }
-            );
+        describe('variant #2: square brackets identifier', () => {
+            const regExp: RegExp = /var *test *= *console\[identifier\];/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/square-brackets-with-identifier-call.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *console\[identifier\];/);
+            it('should ignore square brackets call with identifier value', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
     });
 });

+ 63 - 29
test/functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec.ts

@@ -9,41 +9,75 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('MethodDefinitionTransformer', () => {
-    let code: string = readFileAsString(__dirname + '/fixtures/input.js');
+    const code: string = readFileAsString(__dirname + '/fixtures/input.js');
 
-    it('should replace method definition node `key` property with unicode value', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            code,
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
+    describe('variant #1: default behaviour', () => {
+        const regExp: RegExp = /\['bar'\]\(\)\{\}/;
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /\['bar'\]\(\)\{\}/);
+        let obfuscatedCode: string;
+
+        before(() => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should replace method definition node `key` property with square brackets literal', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 
-    it('should replace method definition node `key` property with unicode array call', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            code,
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                stringArray: true,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4} *= *\['bar'\];/);
-        assert.match(obfuscationResult.getObfuscatedCode(),  /\[_0x([a-f0-9]){4}\('0x0'\)\]\(\)\{\}/);
+    describe('variant #2: `stringArray` option is enabled', () => {
+        const stringArrayRegExp: RegExp = /var *_0x([a-f0-9]){4} *= *\['bar'\];/;
+        const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\('0x0'\)\]\(\)\{\}/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    stringArray: true,
+                    stringArrayThreshold: 1
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should add method definition node `key` property to string array', () => {
+            assert.match(obfuscatedCode,  stringArrayRegExp);
+        });
+
+        it('should replace method definition node `key` property with call to string array', () => {
+            assert.match(obfuscatedCode,  stringArrayCallRegExp);
+        });
     });
 
-    it('should not transform method definition node with `constructor` key', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            code,
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
+    describe('variant #3: `constructor` key', () => {
+        const regExp: RegExp = /constructor\(\)\{\}/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /constructor\(\)\{\}/);
+        it('shouldn\'t transform method definition node with `constructor` key', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 });

+ 36 - 32
test/functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec.ts

@@ -10,57 +10,61 @@ import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('TemplateLiteralTransformer', () => {
     describe('variant #1: simple template literal', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                unicodeEscapeSequence: false
-            }
-        );
-
         it('should transform es6 template literal to es5', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
             assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *'abc\\x20' *\+ *foo;$/);
         });
     });
 
     describe('variant #1: simple template literal with expression only', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/expression-only.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                unicodeEscapeSequence: false
-            }
-        );
-
         it('should transform es6 template literal to es5 and add empty literal node before expression node', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/expression-only.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
             assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *'' *\+ *foo;$/);
         });
     });
 
     describe('variant #3: literal node inside expression', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/literal-inside-expression.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                unicodeEscapeSequence: false
-            }
-        );
-
         it('should transform es6 template literal to es5', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/literal-inside-expression.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
             assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *'abc';$/);
         });
     });
 
     describe('variant #4: multiple expressions', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/multiple-expressions.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                unicodeEscapeSequence: false
-            }
-        );
-
         it('should transform es6 template literal to es5', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/multiple-expressions.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    unicodeEscapeSequence: false
+                }
+            );
+
             assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *0x1 *\+ *0x1 *\+ *'\\x20abc\\x20' *\+ *\(0x1 *\+ *0x1\);$/);
         });
     });

+ 189 - 120
test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts

@@ -12,130 +12,176 @@ describe('DeadCodeInjectionTransformer', () => {
     const variableMatch: string = '_0x([a-f0-9]){4,6}';
     const hexMatch: string = '0x[a-f0-9]';
 
-    describe('transformNode (programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node', () => {
+    describe('transformNode (programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node', function () {
+        this.timeout(100000);
+
         describe('variant #1 - 5 simple block statements', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-1.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    deadCodeInjection: true,
-                    deadCodeInjectionThreshold: 1,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const deadCodeMatch: string = `` +
+            const regExp: RegExp = new RegExp(
                 `if *\\(${variableMatch}\\('${hexMatch}'\\) *[=|!]== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{`+
                     `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
                 `\\} *else *\\{`+
                     `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
-                `\\}` +
-            ``;
-            const regexp: RegExp = new RegExp(deadCodeMatch, 'g');
-            const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
+                `\\}`,
+                'g'
+            );
+            const expectedMatchesLength: number = 5;
+
+            let matchesLength: number = 0;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 1,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
+
+                if (matches) {
+                    matchesLength = matches.length;
+                }
+            });
 
             it('should replace block statements with condition with original block statements and dead code', () => {
-                assert.isNotNull(matches);
-                assert.equal(matches.length, 5);
+                assert.equal(matchesLength, expectedMatchesLength);
             });
         });
 
-        describe('variant #2 - 4 simple block statements', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/block-statements-min-count.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    deadCodeInjection: true,
-                    deadCodeInjectionThreshold: 1,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const codeMatch: string = `` +
+        describe('variant #2 - block statements count is less than `5`', () => {
+            const regexp: RegExp = new RegExp(
                 `var *${variableMatch} *= *function *\\(\\) *\\{` +
                     `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
-                `\\};` +
-            ``;
-            const regexp: RegExp = new RegExp(codeMatch, 'g');
-            const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
+                `\\};`,
+                'g'
+            );
+            const expectedMatchesLength: number = 4;
+
+            let matchesLength: number = 0;
 
-            it('shouldn\'t add dead code if block statements count is less than 5', () => {
-                assert.isNotNull(matches);
-                assert.equal(matches.length, 4);
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/block-statements-min-count.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 1,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
+
+                if (matches) {
+                    matchesLength = matches.length;
+                }
+            });
+
+            it('shouldn\'t add dead code', () => {
+                assert.equal(matchesLength, expectedMatchesLength);
             });
         });
 
         describe('variant #3 - deadCodeInjectionThreshold: 0', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/input-1.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    deadCodeInjection: true,
-                    deadCodeInjectionThreshold: 0,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-            const codeMatch: string = `` +
+            const regexp: RegExp = new RegExp(
                 `var *${variableMatch} *= *function *\\(\\) *\\{` +
                     `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
-                `\\};` +
-            ``;
-            const regexp: RegExp = new RegExp(codeMatch, 'g');
-            const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
+                `\\};`,
+                'g'
+            );
+            const expectedMatchesLength: number = 5;
+
+            let matchesLength: number = 0;
 
-            it('shouldn\'t add dead code if `deadCodeInjectionThreshold` option value is `0`', () => {
-                assert.isNotNull(matches);
-                assert.equal(matches.length, 5);
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 0,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
+
+                if (matches) {
+                    matchesLength = matches.length;
+                }
+            });
+
+            it('shouldn\'t add dead code', () => {
+                assert.equal(matchesLength, expectedMatchesLength);
             });
         });
 
         describe('variant #4 - break or continue statement in block statement', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/break-continue-statement.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    deadCodeInjection: true,
-                    deadCodeInjectionThreshold: 1,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-            const functionMatch: string = `` +
+            const functionRegExp: RegExp = new RegExp(
                 `var *${variableMatch} *= *function *\\(\\) *\\{` +
                     `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
-                `\\};` +
-            ``;
-            const loopMatch: string = `` +
+                `\\};`,
+                'g'
+            );
+            const loopRegExp: RegExp = new RegExp(
                 `for *\\(var *${variableMatch} *= *${hexMatch}; *${variableMatch} *< *${hexMatch}; *${variableMatch}\\+\\+\\) *\\{` +
                     `(?:continue|break);` +
-                `\\}` +
-            ``;
+                `\\}`,
+                'g'
+            );
+            const expectedFunctionMatchesLength: number = 4;
+            const expectedLoopMatchesLength: number = 2;
 
-            const functionRegExp: RegExp = new RegExp(functionMatch, 'g');
-            const loopRegExp: RegExp = new RegExp(loopMatch, 'g');
+            let functionMatchesLength: number = 0,
+                loopMatchesLength: number = 0;
 
-            const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
-            const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/break-continue-statement.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 1,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
+                const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
 
-            it('shouldn\'t add dead code if block statement contains `continue` or `break` statements', () => {
-                assert.isNotNull(functionMatches);
-                assert.isNotNull(loopMatches);
+                if (functionMatches) {
+                    functionMatchesLength = functionMatches.length;
+                }
 
-                assert.equal(functionMatches.length, 4);
-                assert.equal(loopMatches.length, 2);
+                if (loopMatches) {
+                    loopMatchesLength = loopMatches.length;
+                }
+            });
+
+            it('match #1: shouldn\'t add dead code', () => {
+                assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
+            });
+
+            it('match #2: shouldn\'t add dead code', () => {
+                assert.equal(loopMatchesLength, expectedLoopMatchesLength);
             });
         });
 
         describe('variant #5 - chance of `IfStatement` variant', () => {
             const samplesCount: number = 1000;
             const delta: number = 0.1;
-            const expectedValue: number = 0.25;
+            const expectedDistribution: number = 0.25;
 
             const ifMatch: string = `if *\\(!!\\[\\]\\) *\\{`;
             const functionMatch: string = `var *${variableMatch} *= *function *\\(\\) *\\{`;
@@ -169,45 +215,68 @@ describe('DeadCodeInjectionTransformer', () => {
                 `\\}` +
             ``;
 
-            const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
-            const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
-            const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
-            const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
+            let distribution1: number = 0,
+                distribution2: number = 0,
+                distribution3: number = 0,
+                distribution4: number = 0;
 
-            let count1: number = 0;
-            let count2: number = 0;
-            let count3: number = 0;
-            let count4: number = 0;
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js');
 
-            for (let i = 0; i < samplesCount; i++) {
-                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                    readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js'),
-                    {
-                        ...NO_CUSTOM_NODES_PRESET,
-                        deadCodeInjection: true,
-                        deadCodeInjectionThreshold: 1,
-                        stringArray: true,
-                        stringArrayThreshold: 1
-                    }
-                );
-                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
+                const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
+                const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
+                const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
+
+                let count1: number = 0;
+                let count2: number = 0;
+                let count3: number = 0;
+                let count4: number = 0;
+
+                for (let i = 0; i < samplesCount; i++) {
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_CUSTOM_NODES_PRESET,
+                            deadCodeInjection: true,
+                            deadCodeInjectionThreshold: 1,
+                            stringArray: true,
+                            stringArrayThreshold: 1
+                        }
+                    );
+                    const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-                if (regExp1.test(obfuscatedCode)) {
-                    count1++;
-                } else if (regExp2.test(obfuscatedCode)) {
-                    count2++;
-                } else if (regExp3.test(obfuscatedCode)) {
-                    count3++;
-                } else if (regExp4.test(obfuscatedCode)) {
-                    count4++;
+                    if (regExp1.test(obfuscatedCode)) {
+                        count1++;
+                    } else if (regExp2.test(obfuscatedCode)) {
+                        count2++;
+                    } else if (regExp3.test(obfuscatedCode)) {
+                        count3++;
+                    } else if (regExp4.test(obfuscatedCode)) {
+                        count4++;
+                    }
                 }
-            }
 
-            it('each of four `IfStatement` variant should have distribution close to `0.25`', () => {
-                assert.closeTo(count1 / samplesCount, expectedValue, delta);
-                assert.closeTo(count2 / samplesCount, expectedValue, delta);
-                assert.closeTo(count3 / samplesCount, expectedValue, delta);
-                assert.closeTo(count4 / samplesCount, expectedValue, delta);
+                distribution1 = count1 / samplesCount;
+                distribution2 = count2 / samplesCount;
+                distribution3 = count3 / samplesCount;
+                distribution4 = count4 / samplesCount;
+            });
+
+            it('variant #1: `IfStatement` variant should have distribution close to `0.25`', () => {
+                assert.closeTo(distribution1, expectedDistribution, delta);
+            });
+
+            it('variant #2: `IfStatement` variant should have distribution close to `0.25`', () => {
+                assert.closeTo(distribution2, expectedDistribution, delta);
+            });
+
+            it('variant #3: `IfStatement` variant should have distribution close to `0.25`', () => {
+                assert.closeTo(distribution3, expectedDistribution, delta);
+            });
+
+            it('variant #4: `IfStatement` variant should have distribution close to `0.25`', () => {
+                assert.closeTo(distribution4, expectedDistribution, delta);
             });
         });
     });

+ 45 - 27
test/functional-tests/node-transformers/obfuscating-transformers/catch-clause-transformer/CatchClauseTransformer.spec.ts

@@ -9,49 +9,67 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('CatchClauseTransformer', () => {
+    let obfuscatedCode: string;
+
     describe('changeControlFlow (catchClauseNode: ESTree.CatchClause): void', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
         const paramNameRegExp: RegExp = /catch *\((_0x([a-f0-9]){4,6})\) *\{/;
         const bodyParamNameRegExp: RegExp = /console\['log'\]\((_0x([a-f0-9]){4,6})\);/;
 
-        it('should transform catch clause node', () => {
-            assert.match(obfuscatedCode, paramNameRegExp);
-            assert.match(obfuscatedCode, bodyParamNameRegExp);
-        });
+        let firstMatch: string | undefined,
+            secondMatch: string | undefined;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
 
-        it('catch clause arguments param name and param name in body should be same', () => {
             const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(paramNameRegExp);
             const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(bodyParamNameRegExp);
 
-            const firstMatch: string | undefined = firstMatchArray ? firstMatchArray[1] : undefined;
-            const secondMatch: string | undefined = secondMatchArray ? secondMatchArray[1] : undefined;
+            firstMatch = firstMatchArray ? firstMatchArray[1] : undefined;
+            secondMatch = secondMatchArray ? secondMatchArray[1] : undefined;
+        });
+
+        it('match #1: should transform catch clause node', () => {
+            assert.match(obfuscatedCode, paramNameRegExp);
+        });
+
+        it('match #2: should transform catch clause node', () => {
+            assert.match(obfuscatedCode, bodyParamNameRegExp);
+        });
 
-            assert.isOk(firstMatch);
-            assert.isOk(secondMatch);
+        it('catch clause arguments param name and param name in body should be same', () => {
             assert.equal(firstMatch, secondMatch);
         });
     });
 
     describe('object pattern as parameter', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/object-pattern-as-parameter.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-        it('shouldn\'t transform function parameter object pattern identifier', () => {
-            const functionParameterMatch: RegExp = /\} *catch *\(\{ *name *\}\) *\{/;
-            const functionBodyMatch: RegExp = /return *name;/;
+        const functionParameterMatch: RegExp = /\} *catch *\(\{ *name *\}\) *\{/;
+        const functionBodyMatch: RegExp = /return *name;/;
 
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/object-pattern-as-parameter.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('match #1: shouldn\'t transform function parameter object pattern identifier', () => {
             assert.match(obfuscatedCode, functionParameterMatch);
+        });
+
+        it('match #2: shouldn\'t transform function parameter object pattern identifier', () => {
             assert.match(obfuscatedCode, functionBodyMatch);
         });
     });

+ 52 - 26
test/functional-tests/node-transformers/obfuscating-transformers/function-declaration-transformer/FunctionDeclarationTransformer.spec.ts

@@ -10,36 +10,62 @@ import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('FunctionDeclarationTransformer', () => {
     describe('transformation of `functionDeclaration` node names', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-        it('shouldn\'t transform function name if `functionDeclaration` parent block scope is a `ProgramNode`', () => {
-            const functionNameIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-                .match(/function *foo *\(\) *\{/);
-            const functionCallIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-                .match(/foo *\( *\);/);
-
-            const functionParamIdentifierName: string = (<RegExpMatchArray>functionNameIdentifierMatch)[1];
-            const functionBodyIdentifierName: string = (<RegExpMatchArray>functionCallIdentifierMatch)[1];
-
-            assert.equal(functionParamIdentifierName, functionBodyIdentifierName);
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        describe('variant #1: `functionDeclaration` parent block scope is not a `ProgramNode`', () => {
+            const functionNameIdentifierRegExp: RegExp = /function *_0x[a-f0-9]{4,6} *\(\) *\{/;
+            const functionCallIdentifierRegExp: RegExp = /_0x[a-f0-9]{4,6} *\( *\);/;
+
+            let functionParamIdentifierName: string,
+                functionBodyIdentifierName: string;
+
+            before(() => {
+                const functionNameIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                    .match(functionNameIdentifierRegExp);
+                const functionCallIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                    .match(functionCallIdentifierRegExp);
+
+                functionParamIdentifierName = (<RegExpMatchArray>functionNameIdentifierMatch)[1];
+                functionBodyIdentifierName = (<RegExpMatchArray>functionCallIdentifierMatch)[1];
+            });
+
+            it('should transform function name', () => {
+                assert.equal(functionParamIdentifierName, functionBodyIdentifierName);
+            });
         });
 
-        it('should transform function name if `functionDeclaration` parent block scope is not a `ProgramNode`', () => {
-            const functionNameIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-                .match(/function *_0x[a-f0-9]{4,6} *\(\) *\{/);
-            const functionCallIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-                .match(/_0x[a-f0-9]{4,6} *\( *\);/);
+        describe('variant #2: `functionDeclaration` parent block scope is a `ProgramNode`', () => {
+            const functionNameIdentifierRegExp: RegExp = /function *foo *\(\) *\{/;
+            const functionCallIdentifierRegExp: RegExp = /foo *\( *\);/;
+
+            let functionParamIdentifierName: string,
+                functionBodyIdentifierName: string;
+
+            before(() => {
+                const functionNameIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                    .match(functionNameIdentifierRegExp);
+                const functionCallIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                    .match(functionCallIdentifierRegExp);
 
-            const functionParamIdentifierName: string = (<RegExpMatchArray>functionNameIdentifierMatch)[1];
-            const functionBodyIdentifierName: string = (<RegExpMatchArray>functionCallIdentifierMatch)[1];
+                functionParamIdentifierName = (<RegExpMatchArray>functionNameIdentifierMatch)[1];
+                functionBodyIdentifierName = (<RegExpMatchArray>functionCallIdentifierMatch)[1];
+            });
 
-            assert.equal(functionParamIdentifierName, functionBodyIdentifierName);
+            it('shouldn\'t transform function name', () => {
+                assert.equal(functionParamIdentifierName, functionBodyIdentifierName);
+            });
         });
     });
 });

+ 183 - 100
test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec.ts

@@ -10,91 +10,136 @@ import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('FunctionTransformer', () => {
     describe('identifiers transformation inside `FunctionDeclaration` and `FunctionExpression` node body', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-        it('should correct transform both function parameter identifier and function body identifier with same name', () => {
+        const functionParamIdentifierRegExp: RegExp = /var _0x[a-f0-9]{4,6} *= *function *\((_0x[a-f0-9]{4,6})\) *\{/;
+        const functionBodyIdentifierRegExp: RegExp = /console\['log'\]\((_0x[a-f0-9]{4,6})\)/;
+        const variableRegExp: RegExp = /variable *= *0x6;/;
+
+        let obfuscatedCode: string,
+            functionParamIdentifierName: string,
+            functionBodyIdentifierName: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
             const functionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-                .match(/var _0x[a-f0-9]{4,6} *= *function *\((_0x[a-f0-9]{4,6})\) *\{/);
+                .match(functionParamIdentifierRegExp);
             const functionBodyIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-                .match(/console\['log'\]\((_0x[a-f0-9]{4,6})\)/);
+                .match(functionBodyIdentifierRegExp);
 
-            const functionParamIdentifierName: string = (<RegExpMatchArray>functionParamIdentifierMatch)[1];
-            const functionBodyIdentifierName: string = (<RegExpMatchArray>functionBodyIdentifierMatch)[1];
+            functionParamIdentifierName = (<RegExpMatchArray>functionParamIdentifierMatch)[1];
+            functionBodyIdentifierName = (<RegExpMatchArray>functionBodyIdentifierMatch)[1];
+        });
 
+        it('should correctly transform both function parameter identifier and function body identifier with same name', () => {
             assert.equal(functionParamIdentifierName, functionBodyIdentifierName);
         });
 
         it('shouldn\'t transform other variables in function body', () => {
-            assert.equal(/variable *= *0x6;/.test(obfuscatedCode), true);
+            assert.match(obfuscatedCode, variableRegExp);
         });
     });
 
     describe('object pattern as parameter', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/object-pattern-as-parameter.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-        it('shouldn\'t transform function parameter object pattern identifier', () => {
-            const functionParameterMatch: RegExp = /function *\(\{ *bar *\}\) *\{/;
-            const functionBodyMatch: RegExp = /return *bar;/;
-
-            assert.match(obfuscatedCode, functionParameterMatch);
-            assert.match(obfuscatedCode, functionBodyMatch);
-        });
-    });
+        const functionParameterRegExp: RegExp = /function *\(\{ *bar *\}\) *\{/;
+        const functionBodyRegExp: RegExp = /return *bar;/;
 
-    describe('assignment pattern as parameter', () => {
-        describe('literal as right value', () => {
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/object-pattern-as-parameter.js');
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-1.js'),
+                code,
                 {
                     ...NO_CUSTOM_NODES_PRESET
                 }
             );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-            it('should transform function parameter assignment pattern identifier', () => {
-                const functionParameterMatch: RegExp = /function *\(_0x[a-f0-9]{4,6} *= *0x1\) *\{/;
-                const functionBodyMatch: RegExp = /return *_0x[a-f0-9]{4,6};/;
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
 
-                assert.match(obfuscatedCode, functionParameterMatch);
-                assert.match(obfuscatedCode, functionBodyMatch);
-            });
+        it('match #1: shouldn\'t transform function parameter object pattern identifier', () => {
+            assert.match(obfuscatedCode, functionParameterRegExp);
         });
 
-        describe('identifier as right value', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-2.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+        it('match #2: shouldn\'t transform function parameter object pattern identifier', () => {
+            assert.match(obfuscatedCode, functionBodyRegExp);
+        });
+    });
+
+    describe('assignment pattern as parameter', () => {
+        describe('variant #1: literal as right value', () => {
+            const functionParameterRegExp: RegExp = /function *\(_0x[a-f0-9]{4,6} *= *0x1\) *\{/;
+            const functionBodyRegExp: RegExp = /return *_0x[a-f0-9]{4,6};/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('match #1: should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, functionParameterRegExp);
+            });
+
+            it('match #2: should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, functionBodyRegExp);
+            });
+        });
 
-            const variableDeclarationMatch: RegExp = /var *(_0x[a-f0-9]{4,6}) *= *0x1;/;
-            const functionParameterMatch: RegExp = /function *\((_0x[a-f0-9]{4,6}) *= *(_0x[a-f0-9]{4,6})\) *\{/;
-            const functionBodyMatch: RegExp = /return *(_0x[a-f0-9]{4,6});/;
+        describe('variant #2: identifier as right value', () => {
+            const variableDeclarationRegExp: RegExp = /var *(_0x[a-f0-9]{4,6}) *= *0x1;/;
+            const functionParameterRegExp: RegExp = /function *\((_0x[a-f0-9]{4,6}) *= *(_0x[a-f0-9]{4,6})\) *\{/;
+            const functionBodyRegExp: RegExp = /return *(_0x[a-f0-9]{4,6});/;
+
+            let obfuscatedCode: string,
+                variableDeclarationIdentifierName: string,
+                functionParameterIdentifierName: string,
+                functionDefaultParameterIdentifierName: string,
+                functionBodyIdentifierName: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-2.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+                variableDeclarationIdentifierName = obfuscatedCode.match(variableDeclarationRegExp)![1];
+                functionParameterIdentifierName = obfuscatedCode.match(functionParameterRegExp)![1];
+                functionDefaultParameterIdentifierName = obfuscatedCode.match(functionParameterRegExp)![2];
+                functionBodyIdentifierName = obfuscatedCode.match(functionBodyRegExp)![1];
+            });
 
-            const variableDeclarationIdentifierName: string = obfuscatedCode.match(variableDeclarationMatch)![1];
-            const functionParameterIdentifierName: string = obfuscatedCode.match(functionParameterMatch)![1];
-            const functionDefaultParameterIdentifierName: string = obfuscatedCode.match(functionParameterMatch)![2];
+            it('match #1: should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, variableDeclarationRegExp);
+            });
 
-            const functionBodyIdentifierName: string = obfuscatedCode.match(functionBodyMatch)![1];
+            it('match #2: should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, functionParameterRegExp);
+            });
 
-            it('should transform function parameter assignment pattern identifier', () => {
-                assert.match(obfuscatedCode, variableDeclarationMatch);
-                assert.match(obfuscatedCode, functionParameterMatch);
-                assert.match(obfuscatedCode, functionBodyMatch);
+            it('match #3: should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, functionBodyRegExp);
             });
 
             it('should keep same names for identifier in variable declaration and default value identifier of function parameter', () => {
@@ -106,36 +151,60 @@ describe('FunctionTransformer', () => {
             });
         });
 
-        describe('identifier as right value', () => {
-            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-3.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET
-                }
-            );
-            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-            const variableDeclarationMatch: RegExp = /var *(_0x[a-f0-9]{4,6}) *= *0x1;/;
-            const functionParameterMatch: RegExp = /function *\((_0x[a-f0-9]{4,6}), *(_0x[a-f0-9]{4,6}) *= *(_0x[a-f0-9]{4,6})\) *\{/;
-            const functionBodyMatch: RegExp = /return *(_0x[a-f0-9]{4,6}) *\+ *(_0x[a-f0-9]{4,6});/;
+        describe('variant #3: identifier as right value', () => {
+            const variableDeclarationRegExp: RegExp = /var *(_0x[a-f0-9]{4,6}) *= *0x1;/;
+            const functionParameterRegExp: RegExp = /function *\((_0x[a-f0-9]{4,6}), *(_0x[a-f0-9]{4,6}) *= *(_0x[a-f0-9]{4,6})\) *\{/;
+            const functionBodyRegExp: RegExp = /return *(_0x[a-f0-9]{4,6}) *\+ *(_0x[a-f0-9]{4,6});/;
+
+            let obfuscatedCode: string,
+                variableDeclarationIdentifierName: string,
+                functionParameterIdentifierName: string,
+                functionDefaultParameterIdentifierName1: string,
+                functionDefaultParameterIdentifierName2: string,
+                functionBodyIdentifierName1: string,
+                functionBodyIdentifierName2: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-3.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+                variableDeclarationIdentifierName = obfuscatedCode.match(variableDeclarationRegExp)![1];
+                functionParameterIdentifierName = obfuscatedCode.match(functionParameterRegExp)![1];
+                functionDefaultParameterIdentifierName1 = obfuscatedCode.match(functionParameterRegExp)![2];
+                functionDefaultParameterIdentifierName2 = obfuscatedCode.match(functionParameterRegExp)![3];
+
+                functionBodyIdentifierName1 = obfuscatedCode.match(functionBodyRegExp)![1];
+                functionBodyIdentifierName2 = obfuscatedCode.match(functionBodyRegExp)![2];
+            });
 
-            const variableDeclarationIdentifierName: string = obfuscatedCode.match(variableDeclarationMatch)![1];
-            const functionParameterIdentifierName: string = obfuscatedCode.match(functionParameterMatch)![1];
-            const functionDefaultParameterIdentifierName1: string = obfuscatedCode.match(functionParameterMatch)![2];
-            const functionDefaultParameterIdentifierName2: string = obfuscatedCode.match(functionParameterMatch)![3];
+            it('match #1: should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, variableDeclarationRegExp);
+            });
 
-            const functionBodyIdentifierName1: string = obfuscatedCode.match(functionBodyMatch)![1];
-            const functionBodyIdentifierName2: string = obfuscatedCode.match(functionBodyMatch)![2];
+            it('match #2: should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, functionParameterRegExp);
+            });
 
-            it('should transform function parameter assignment pattern identifier', () => {
-                assert.match(obfuscatedCode, variableDeclarationMatch);
-                assert.match(obfuscatedCode, functionParameterMatch);
-                assert.match(obfuscatedCode, functionBodyMatch);
+            it('match #3: should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, functionBodyRegExp);
             });
 
-            it('shouldn\'t keep same names variable declaration identifier and function parameters identifiers', () => {
+            it('equal #1: shouldn\'t keep same names variable declaration identifier and function parameters identifiers', () => {
                 assert.notEqual(variableDeclarationIdentifierName, functionParameterIdentifierName);
+            });
+
+            it('equal #2: shouldn\'t keep same names variable declaration identifier and function parameters identifiers', () => {
                 assert.notEqual(variableDeclarationIdentifierName, functionDefaultParameterIdentifierName1);
+            });
+
+            it('equal #3: shouldn\'t keep same names variable declaration identifier and function parameters identifiers', () => {
                 assert.notEqual(variableDeclarationIdentifierName, functionDefaultParameterIdentifierName2);
             });
 
@@ -143,32 +212,46 @@ describe('FunctionTransformer', () => {
                 assert.equal(functionParameterIdentifierName, functionDefaultParameterIdentifierName2);
             });
 
-            it('should keep same names for identifiers in function params and function body', () => {
+            it('equal #1: should keep same names for identifiers in function params and function body', () => {
                 assert.equal(functionParameterIdentifierName, functionBodyIdentifierName1);
+            });
+
+            it('equal #2: should keep same names for identifiers in function params and function body', () => {
                 assert.equal(functionDefaultParameterIdentifierName1, functionBodyIdentifierName2);
             });
         });
     });
 
     describe('array pattern as parameter', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/array-pattern-as-parameter.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-        const functionParameterMatch: RegExp = /function *\(\[(_0x[a-f0-9]{4,6}), *(_0x[a-f0-9]{4,6})\]\) *\{/;
-        const functionBodyMatch: RegExp = /return *(_0x[a-f0-9]{4,6}) *\+ *(_0x[a-f0-9]{4,6});/;
-
-        const arrayPatternIdentifierName1: string = obfuscatedCode.match(functionParameterMatch)![1];
-        const arrayPatternIdentifierName2: string = obfuscatedCode.match(functionParameterMatch)![2];
-        const functionBodyIdentifierName1: string = obfuscatedCode.match(functionBodyMatch)![1];
-        const functionBodyIdentifierName2: string = obfuscatedCode.match(functionBodyMatch)![2];
-
-        it('should keep same names for identifiers in function parameter array pattern and function body', () => {
+        const functionParameterRegExp: RegExp = /function *\(\[(_0x[a-f0-9]{4,6}), *(_0x[a-f0-9]{4,6})\]\) *\{/;
+        const functionBodyRegExp: RegExp = /return *(_0x[a-f0-9]{4,6}) *\+ *(_0x[a-f0-9]{4,6});/;
+
+        let arrayPatternIdentifierName1: string,
+            arrayPatternIdentifierName2: string,
+            functionBodyIdentifierName1: string,
+            functionBodyIdentifierName2: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/array-pattern-as-parameter.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+            arrayPatternIdentifierName1 = obfuscatedCode.match(functionParameterRegExp)![1];
+            arrayPatternIdentifierName2 = obfuscatedCode.match(functionParameterRegExp)![2];
+            functionBodyIdentifierName1 = obfuscatedCode.match(functionBodyRegExp)![1];
+            functionBodyIdentifierName2 = obfuscatedCode.match(functionBodyRegExp)![2];
+        });
+
+        it('equal #1: should keep same names for identifiers in function parameter array pattern and function body', () => {
             assert.equal(arrayPatternIdentifierName1, functionBodyIdentifierName1);
+        });
+
+        it('equal #2: should keep same names for identifiers in function parameter array pattern and function body', () => {
             assert.equal(arrayPatternIdentifierName2, functionBodyIdentifierName2);
         });
     });

+ 29 - 19
test/functional-tests/node-transformers/obfuscating-transformers/labeled-statement-transformer/LabeledStatementTransformer.spec.ts

@@ -10,17 +10,35 @@ import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('LabeledStatementTransformer', () => {
     describe('changeControlFlow (labeledStatementNode: ESTree.LabeledStatement): void', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/input.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
         const labeledStatementRegExp: RegExp = /(_0x([a-f0-9]){4,6}): *\{/;
         const continueStatementRegExp: RegExp = /continue *(_0x([a-f0-9]){4,6});/;
         const breakStatementRegExp: RegExp = /break *(_0x([a-f0-9]){4,6});/;
 
+        let obfuscatedCode: string,
+            firstMatch: string|undefined,
+            secondMatch: string|undefined,
+            thirdMatch: string|undefined;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+            const firstMatchArray: RegExpMatchArray|null = obfuscatedCode.match(labeledStatementRegExp);
+            const secondMatchArray: RegExpMatchArray|null = obfuscatedCode.match(continueStatementRegExp);
+            const thirdMatchArray: RegExpMatchArray|null = obfuscatedCode.match(breakStatementRegExp);
+
+            firstMatch = firstMatchArray ? firstMatchArray[1] : undefined;
+            secondMatch = secondMatchArray ? secondMatchArray[1] : undefined;
+            thirdMatch = thirdMatchArray ? thirdMatchArray[1] : undefined;
+        });
+
         it('should transform `labeledStatement` identifier', () => {
             assert.match(obfuscatedCode, labeledStatementRegExp);
         });
@@ -33,19 +51,11 @@ describe('LabeledStatementTransformer', () => {
             assert.match(obfuscatedCode, breakStatementRegExp);
         });
 
-        it('`labeledStatement` identifier name and `labeledStatement` body `breakStatement` should be same', () => {
-            const firstMatchArray: RegExpMatchArray|null = obfuscatedCode.match(labeledStatementRegExp);
-            const secondMatchArray: RegExpMatchArray|null = obfuscatedCode.match(continueStatementRegExp);
-            const thirdMatchArray: RegExpMatchArray|null = obfuscatedCode.match(breakStatementRegExp);
-
-            const firstMatch: string|undefined = firstMatchArray ? firstMatchArray[1] : undefined;
-            const secondMatch: string|undefined = secondMatchArray ? secondMatchArray[1] : undefined;
-            const thirdMatch: string|undefined = thirdMatchArray ? thirdMatchArray[1] : undefined;
-
-            assert.isOk(firstMatch);
-            assert.isOk(secondMatch);
-            assert.isOk(thirdMatchArray);
+        it('equal #1: `labeledStatement` identifier name and `labeledStatement` body `breakStatement` should be same', () => {
             assert.equal(firstMatch, secondMatch);
+        });
+
+        it('equal #2: `labeledStatement` identifier name and `labeledStatement` body `breakStatement` should be same', () => {
             assert.equal(secondMatch, thirdMatch);
         });
     });

+ 301 - 150
test/functional-tests/node-transformers/obfuscating-transformers/literal-transformer/LiteralTransformer.spec.ts

@@ -10,203 +10,354 @@ import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('LiteralTransformer', () => {
     describe('transformation of literal node with string value', () => {
-        it('should replace literal node value with value from string array', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/simple-input.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
+        describe('variant #1: default behaviour', () => {
+            const stringArrayRegExp: RegExp = /^var *_0x([a-f0-9]){4} *= *\['test'\];/;
+            const stringArrayCallRegExp: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                /^var *_0x([a-f0-9]){4} *= *\['test'\];/
-            );
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('match #1: should replace literal node value with value from string array', () => {
+                assert.match(obfuscatedCode, stringArrayRegExp);
+            });
+
+            it('match #2: should replace literal node value with value from string array', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
         });
 
-        it('shouldn\'t replace literal node value with value from string array if `stringArray` option is disabled', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/simple-input.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET
-                }
-            );
+        describe('variant #2: `stringArray` option is disabled', () => {
+            const regExp: RegExp = /^var *test *= *'test';/;
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                /^var *test *= *'test';/
-            );
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t replace literal node value with value from string array', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
 
-        it('should\'t throw an error when string contains non-latin and non-digit characters and `unicodeEscapeSequence` is disabled', () => {
-            assert.doesNotThrow(() => JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/error-when-non-latin.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            ));
+        describe('variant #3: string contains non-latin and non-digit characters and `unicodeEscapeSequence` is disabled', () => {
+            let testFunc: () => void;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/error-when-non-latin.js');
+
+                testFunc = () => JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+            });
+
+            it('should\'t throw an error', () => {
+                assert.doesNotThrow(testFunc);
+            });
         });
 
-        it('should create only one item in string array for same literal node values', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/same-literal-values.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
+        describe('variant #4: same literal node values', () => {
+            const stringArrayRegExp: RegExp = /^var *_0x([a-f0-9]){4} *= *\['test'\];/;
+            const stringArrayCallRegExp: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                /^var *_0x([a-f0-9]){4} *= *\['test'\];/
-            );
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/same-literal-values.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('match #1: should create only one item in string array for same literal node values', () => {
+                assert.match(obfuscatedCode, stringArrayRegExp);
+            });
+
+            it('match #2: should create only one item in string array for same literal node values', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
         });
 
-        it('should replace literal node value with unicode escape sequence if `unicodeEscapeSequence` is enabled', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/simple-input.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    unicodeEscapeSequence: true
+        describe('variant #5: `unicodeEscapeSequence` option is enabled', () => {
+            const regExp: RegExp = /^var *test *= *'\\x74\\x65\\x73\\x74';$/;
 
-                }
-            );
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        unicodeEscapeSequence: true
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *'\\x74\\x65\\x73\\x74';$/);
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should replace literal node value with unicode escape sequence', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
 
-        it('should replace literal node value with unicode escape sequence from string array if `unicodeEscapeSequence` is enabled', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/simple-input.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1,
-                    unicodeEscapeSequence: true
-                }
-            );
+        describe('variant #6: `unicodeEscapeSequence` and `stringArray` options are enabled', () => {
+            const stringArrayRegExp: RegExp = /^var *_0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/;
+            const stringArrayCallRegExp: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                /^var *_0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/
-            );
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        unicodeEscapeSequence: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('match #1: should replace literal node value with unicode escape sequence from string array', () => {
+                assert.match(obfuscatedCode, stringArrayRegExp);
+            });
+
+            it('match #1: should replace literal node value with unicode escape sequence from string array', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
         });
 
-        it('shouldn\'t replace short literal node value with value from string array', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/short-literal-value.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
-                }
-            );
+        describe('variant #7: short literal node value', () => {
+            const regExp: RegExp = /var *test *= *'te';/;
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *'te';/);
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/short-literal-value.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t replace short literal node value with value from string array', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('variant #8: base64 encoding', () => {
+            const stringArrayRegExp: RegExp = /^var *_0x([a-f0-9]){4} *= *\['dGVzdA\\x3d\\x3d'\];/;
+            const stringArrayCallRegExp: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayEncoding: 'base64',
+                        stringArrayThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('match #1: should replace literal node value with value from string array encoded using base64', () => {
+                assert.match(obfuscatedCode, stringArrayRegExp);
+            });
+
+            it('match #2: should replace literal node value with value from string array encoded using base64', () => {
+                assert.match(obfuscatedCode, stringArrayCallRegExp);
+            });
+        });
+
+        describe('variant #9: rc4 encoding', () => {
+            const regExp: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0', *'(?:\w|(?:\\x[a-f0-9]*)){4}'\);/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayEncoding: 'rc4',
+                        stringArrayThreshold: 1
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should replace literal node value with value from string array encoded using rc4', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('variant #10: `stringArrayThreshold` option value', () => {
+            const samples: number = 1000;
+            const stringArrayThreshold: number = 0.5;
+            const delta: number = 0.1;
+
+            const regExp1: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/g;
+            const regExp2: RegExp = /var *test *= *'test';/g;
+
+            let stringArrayProbability: number,
+                noStringArrayProbability: number;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    `${code}\n`.repeat(samples),
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: stringArrayThreshold
+                    }
+                );
+
+                const stringArrayMatchesLength: number = obfuscationResult
+                    .getObfuscatedCode()
+                    .match(regExp1)!
+                    .length;
+                const noStringArrayMatchesLength: number = obfuscationResult
+                    .getObfuscatedCode()
+                    .match(regExp2)!
+                    .length;
+
+                stringArrayProbability = stringArrayMatchesLength / samples;
+                noStringArrayProbability = noStringArrayMatchesLength / samples;
+            });
+
+            it('variant #1: should replace literal node value with value from string array with `stringArrayThreshold` chance', () => {
+                assert.closeTo(stringArrayProbability, stringArrayThreshold, delta);
+            });
+
+            it('variant #2: shouldn\'t replace literal node value with value from string array with `(1 - stringArrayThreshold)` chance', () => {
+                assert.closeTo(noStringArrayProbability, stringArrayThreshold, delta);
+            });
         });
+    });
+
+    describe('transformation of literal node with boolean value', () => {
+        const regExp: RegExp = /^var *test *= *!!\[\];$/;
 
-        it('should replace literal node value with value from string array encoded using base64', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/simple-input.js'),
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/boolean-value.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
-                    stringArrayEncoding: 'base64',
                     stringArrayThreshold: 1
                 }
             );
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                /^var *_0x([a-f0-9]){4} *= *\['dGVzdA\\x3d\\x3d'\];/
-            );
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/);
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should transform literal node', () => {
+            assert.match(obfuscatedCode, regExp);
         });
+    });
 
-        it('should replace literal node value with value from string array encoded using rc4', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/simple-input.js'),
+    describe('transformation of literal node with number value', () => {
+        const regExp: RegExp = /^var *test *= *0x0;$/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/number-value.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
-                    stringArrayEncoding: 'rc4',
                     stringArrayThreshold: 1
                 }
             );
 
-            assert.match(
-                obfuscationResult.getObfuscatedCode(),
-                /var *test *= *_0x([a-f0-9]){4}\('0x0', *'(?:\w|(?:\\x[a-f0-9]*)){4}'\);/
-            );
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
         });
 
-        it('should replace literal node value with value from string array with `stringArrayThreshold` chance', () => {
-            const samples: number = 1000;
-            const stringArrayThreshold: number = 0.5;
-            const delta: number = 0.1;
+        it('should transform literal node', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
+    });
+
+    describe('RegExp literal', () => {
+        const regExp: RegExp = /^var *regExp *= *\/\(\\d\+\)\/;$/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/regexp-literal.js');
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                `${readFileAsString(__dirname + '/fixtures/simple-input.js')}\n`.repeat(samples),
+                code,
                 {
                     ...NO_CUSTOM_NODES_PRESET,
                     stringArray: true,
-                    stringArrayThreshold: stringArrayThreshold
+                    stringArrayThreshold: 1
                 }
             );
 
-            const regExp1: RegExp = /var *test *= *_0x([a-f0-9]){4}\('0x0'\);/g;
-            const regExp2: RegExp = /var *test *= *'test';/g;
-            const stringArrayMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp1)!.length;
-            const noStringArrayMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp2)!.length;
-
-            assert.closeTo(stringArrayMatchesLength / samples, stringArrayThreshold, delta);
-            assert.closeTo(noStringArrayMatchesLength / samples, stringArrayThreshold, delta);
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
         });
-    });
-
-    it('should transform literal node with boolean value', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/boolean-value.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                stringArray: true,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *!!\[\];$/);
-    });
 
-    it('should transform literal node with number value', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/number-value.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                stringArray: true,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(),  /^var *test *= *0x0;$/);
-    });
-
-    it('should keep safe value of RegExp literal', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/regexp-literal.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                stringArray: true,
-                stringArrayThreshold: 1
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(),  /^var *regExp *= *\/\(\\d\+\)\/;$/);
+        it('should keep safe value of RegExp literal', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 });

+ 40 - 21
test/functional-tests/node-transformers/obfuscating-transformers/object-expression-transformer/ObjectExpressionTransformer.spec.ts

@@ -9,28 +9,47 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('ObjectExpressionTransformer', () => {
-    it('should replace object expression node `key` property with identifier value by property with literal value', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/property-with-identifier-value.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *\{'foo':0x0\};/);
+    describe('default behaviour', () => {
+        const regExp: RegExp = /var *test *= *\{'foo':0x0\};/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/property-with-identifier-value.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+       });
+
+        it('should replace object expression node `key` property with identifier value by property with literal value', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 
-    it('should correct convert shorthand ES6 object expression to non-shorthand object expression', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/shorthand-object-expression.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-
-        assert.match(
-            obfuscationResult.getObfuscatedCode(),
-            /var *_0x[a-f0-9]{4,6} *= *\{'a': *_0x[a-f0-9]{4,6}\, *'b': *_0x[a-f0-9]{4,6}\};/
-        );
+    describe('shorthand ES6 object expression', () => {
+        const regExp: RegExp = /var *_0x[a-f0-9]{4,6} *= *\{'a': *_0x[a-f0-9]{4,6}\, *'b': *_0x[a-f0-9]{4,6}\};/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/shorthand-object-expression.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should correct convert shorthand ES6 object expression to non-shorthand object expression', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 });

+ 297 - 150
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec.ts

@@ -9,106 +9,185 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
 
 describe('VariableDeclarationTransformer', () => {
-    it('should transform `variableDeclaration` node', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/simple-declaration.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-
-        assert.match(obfuscationResult.getObfuscatedCode(),  /var *_0x([a-f0-9]){4,6} *= *'abc';/);
-        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(_0x([a-f0-9]){4,6}\);/);
+    describe('variant #1: default behaviour', () => {
+        const variableDeclarationRegExp: RegExp = /var *_0x([a-f0-9]){4,6} *= *'abc';/;
+        const variableCallRegExp: RegExp = /console\['log'\]\(_0x([a-f0-9]){4,6}\);/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-declaration.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('match #1: should transform `variableDeclaration` node', () => {
+            assert.match(obfuscatedCode, variableDeclarationRegExp);
+        });
+
+        it('match #2: should transform `variableDeclaration` node', () => {
+            assert.match(obfuscatedCode, variableCallRegExp);
+        });
     });
 
-    it('should not transform `variableDeclaration` node if parent block scope node is `Program` node', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/parent-block-scope-is-program-node.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
+    describe('variant #2: parent block scope node is `Program` node', () => {
+        const variableDeclarationRegExp: RegExp = /var *test *= *0xa;/;
+        const variableCallRegExp: RegExp = /console\['log'\]\(test\);/;
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /var *test *= *0xa;/);
-        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(test\);/);
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/parent-block-scope-is-program-node.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('match #1: shouldn\'t transform `variableDeclaration` node', () => {
+            assert.match(obfuscatedCode, variableDeclarationRegExp);
+        });
+
+        it('match #2: shouldn\'t transform `variableDeclaration` node', () => {
+            assert.match(obfuscatedCode, variableCallRegExp);
+        });
     });
 
-    it('should transform variable call (`identifier` node) outside of block scope of node in which this variable was declared with `var` kind', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/var-kind.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
+    describe('variant #3: scope of `var` kind', () => {
+        const regExp: RegExp = /console\['log'\]\(_0x([a-f0-9]){4,6}\);/;
+
+        let obfuscatedCode: string;
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(_0x([a-f0-9]){4,6}\);/);
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/var-kind.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
+
+        it('should transform variable call (`identifier` node) outside of block scope of node in which this variable was declared with `var` kind', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 
-    it('should not transform variable call (`identifier` node) outside of block scope of node in which this variable was declared with `let` kind', () => {
-        let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/let-kind.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
+    describe('variant #4: scope of `let` kind', () => {
+        const regExp: RegExp = /console\['log'\]\(test\);/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/let-kind.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+        });
 
-        assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(test\);/);
+        it('shouldn\'t transform variable call (`identifier` node) outside of block scope of node in which this variable was declared with `let` kind', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
     });
 
-    describe(`variable calls before variable declaration`, () => {
-        let obfuscationResult: IObfuscationResult;
+    describe(`variant #5: variable calls before variable declaration`, () => {
+        const functionBodyVariableCallRegExp: RegExp = /console\['log'\]\(_0x([a-f0-9]){4,6}\['item'\]\);/;
+        const variableCallBeforeDeclarationRegExp: RegExp = /console\['log'\]\(_0x([a-f0-9]){4,6}\);/;
+
+        let obfuscatedCode: string;
 
-        beforeEach(() => {
-            obfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/variable-call-before-variable-declaration-1.js'),
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/variable-call-before-variable-declaration-1.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
                 {
                     ...NO_CUSTOM_NODES_PRESET
                 }
             );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
         });
 
         it('should transform variable call (`identifier` node name) before variable declaration if this call is inside function body', () => {
-            assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(_0x([a-f0-9]){4,6}\['item'\]\);/);
+            assert.match(obfuscatedCode, functionBodyVariableCallRegExp);
         });
 
         it('should transform variable call (`identifier` node name) before variable declaration', () => {
-            assert.match(obfuscationResult.getObfuscatedCode(),  /console\['log'\]\(_0x([a-f0-9]){4,6}\);/);
+            assert.match(obfuscatedCode, variableCallBeforeDeclarationRegExp);
         });
     });
 
-    describe(`variable calls before variable declaration when function param has the same name as variables name`, () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/variable-call-before-variable-declaration-2.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-        const functionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/function *_0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\,(_0x[a-f0-9]{4,6})\) *\{/);
-        const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/);
-        const constructorIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/console\['log'\]\((_0x[a-f0-9]{4,6})\)/);
-        const objectIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/return\{'t':(_0x[a-f0-9]{4,6})\}/);
-        const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/var *(_0x[a-f0-9]{4,6});/);
-
-        const outerFunctionParamIdentifierName: string|null = (<RegExpMatchArray>functionParamIdentifierMatch)[1];
-        const innerFunctionParamIdentifierName: string|null = (<RegExpMatchArray>innerFunctionParamIdentifierMatch)[1];
-        const constructorIdentifierName: string|null = (<RegExpMatchArray>constructorIdentifierMatch)[1];
-        const objectIdentifierName: string|null = (<RegExpMatchArray>objectIdentifierMatch)[1];
-        const variableDeclarationIdentifierName: string|null = (<RegExpMatchArray>variableDeclarationIdentifierMatch)[1];
-
-        it('should\'t name variables inside inner function with names from outer function params', () => {
+    describe(`variant #6: variable calls before variable declaration when function param has the same name as variables name`, () => {
+        const functionParamIdentifierRegExp: RegExp = /function *_0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\,(_0x[a-f0-9]{4,6})\) *\{/;
+        const innerFunctionParamIdentifierRegExp: RegExp = /function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/;
+        const constructorIdentifierRegExp: RegExp = /console\['log'\]\((_0x[a-f0-9]{4,6})\)/;
+        const objectIdentifierRegExp: RegExp = /return\{'t':(_0x[a-f0-9]{4,6})\}/;
+        const variableDeclarationIdentifierRegExp: RegExp = /var *(_0x[a-f0-9]{4,6});/;
+
+        let outerFunctionParamIdentifierName: string|null,
+            innerFunctionParamIdentifierName: string|null,
+            constructorIdentifierName: string|null,
+            objectIdentifierName: string|null,
+            variableDeclarationIdentifierName: string|null;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/variable-call-before-variable-declaration-2.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+            const functionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(functionParamIdentifierRegExp);
+            const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(innerFunctionParamIdentifierRegExp);
+            const constructorIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(constructorIdentifierRegExp);
+            const objectIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(objectIdentifierRegExp);
+            const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(variableDeclarationIdentifierRegExp);
+
+            outerFunctionParamIdentifierName = (<RegExpMatchArray>functionParamIdentifierMatch)[1];
+            innerFunctionParamIdentifierName = (<RegExpMatchArray>innerFunctionParamIdentifierMatch)[1];
+            constructorIdentifierName = (<RegExpMatchArray>constructorIdentifierMatch)[1];
+            objectIdentifierName = (<RegExpMatchArray>objectIdentifierMatch)[1];
+            variableDeclarationIdentifierName = (<RegExpMatchArray>variableDeclarationIdentifierMatch)[1];
+        });
+
+        it('match #1: should\'t name variables inside inner function with names from outer function params', () => {
             assert.notEqual(outerFunctionParamIdentifierName, constructorIdentifierName);
+        });
+
+        it('match #2: should\'t name variables inside inner function with names from outer function params', () => {
             assert.notEqual(outerFunctionParamIdentifierName, innerFunctionParamIdentifierName);
         });
 
-        it('should correct transform variables inside outer function body', () => {
+        it('match #1: should correct transform variables inside outer function body', () => {
             assert.equal(outerFunctionParamIdentifierName, objectIdentifierName);
+        });
+
+        it('match #2: should correct transform variables inside outer function body', () => {
             assert.equal(outerFunctionParamIdentifierName, variableDeclarationIdentifierName);
         });
 
@@ -121,40 +200,61 @@ describe('VariableDeclarationTransformer', () => {
         });
     });
 
-    describe(`variable calls before variable declaration when catch clause param has the same name as variables name`, () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/variable-call-before-variable-declaration-3.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
-
-        const catchClauseParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/catch *\((_0x[a-f0-9]{4,6})\) *\{/);
-        const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/);
-        const constructorIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/console\['log'\]\((_0x[a-f0-9]{4,6})\)/);
-        const objectIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/return\{'t':(_0x[a-f0-9]{4,6})\}/);
-        const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
-            .match(/var *(_0x[a-f0-9]{4,6});/);
-
-        const functionParamIdentifierName: string|null = (<RegExpMatchArray>catchClauseParamIdentifierMatch)[1];
-        const innerFunctionParamIdentifierName: string|null = (<RegExpMatchArray>innerFunctionParamIdentifierMatch)[1];
-        const constructorIdentifierName: string|null = (<RegExpMatchArray>constructorIdentifierMatch)[1];
-        const objectIdentifierName: string|null = (<RegExpMatchArray>objectIdentifierMatch)[1];
-        const variableDeclarationIdentifierName: string|null = (<RegExpMatchArray>variableDeclarationIdentifierMatch)[1];
-
-        it('should\'t name variables inside inner function with names from catch clause param', () => {
-            assert.notEqual(functionParamIdentifierName, constructorIdentifierName);
-            assert.notEqual(functionParamIdentifierName, innerFunctionParamIdentifierName);
-        });
-
-        it('should correct transform variables inside catch clause body', () => {
-            assert.equal(functionParamIdentifierName, objectIdentifierName);
-            assert.equal(functionParamIdentifierName, variableDeclarationIdentifierName);
+    describe(`variant #7: variable calls before variable declaration when catch clause param has the same name as variables name`, () => {
+        const catchClauseParamIdentifierRegExp: RegExp = /catch *\((_0x[a-f0-9]{4,6})\) *\{/;
+        const innerFunctionParamIdentifierRegExp: RegExp = /function _0x[a-f0-9]{4,6} *\((_0x[a-f0-9]{4,6})\) *\{/;
+        const constructorIdentifierRegExp: RegExp = /console\['log'\]\((_0x[a-f0-9]{4,6})\)/;
+        const objectIdentifierRegExp: RegExp = /return\{'t':(_0x[a-f0-9]{4,6})\}/;
+        const variableDeclarationIdentifierRegExp: RegExp = /var *(_0x[a-f0-9]{4,6});/;
+
+        let catchClauseParamIdentifierName: string|null,
+            innerFunctionParamIdentifierName: string|null,
+            constructorIdentifierName: string|null,
+            objectIdentifierName: string|null,
+            variableDeclarationIdentifierName: string|null;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/variable-call-before-variable-declaration-3.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+            const catchClauseParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(catchClauseParamIdentifierRegExp);
+            const innerFunctionParamIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(innerFunctionParamIdentifierRegExp);
+            const constructorIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(constructorIdentifierRegExp);
+            const objectIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(objectIdentifierRegExp);
+            const variableDeclarationIdentifierMatch: RegExpMatchArray|null = obfuscatedCode
+                .match(variableDeclarationIdentifierRegExp);
+
+            catchClauseParamIdentifierName = (<RegExpMatchArray>catchClauseParamIdentifierMatch)[1];
+            innerFunctionParamIdentifierName = (<RegExpMatchArray>innerFunctionParamIdentifierMatch)[1];
+            constructorIdentifierName = (<RegExpMatchArray>constructorIdentifierMatch)[1];
+            objectIdentifierName = (<RegExpMatchArray>objectIdentifierMatch)[1];
+            variableDeclarationIdentifierName = (<RegExpMatchArray>variableDeclarationIdentifierMatch)[1];
+        });
+
+        it('match #1: should\'t name variables inside inner function with names from catch clause param', () => {
+            assert.notEqual(catchClauseParamIdentifierName, constructorIdentifierName);
+        });
+
+        it('match #2: should\'t name variables inside inner function with names from catch clause param', () => {
+            assert.notEqual(catchClauseParamIdentifierName, innerFunctionParamIdentifierName);
+        });
+
+        it('equal #1: should correct transform variables inside catch clause body', () => {
+            assert.equal(catchClauseParamIdentifierName, objectIdentifierName);
+        });
+
+        it('equal #2: should correct transform variables inside catch clause body', () => {
+            assert.equal(catchClauseParamIdentifierName, variableDeclarationIdentifierName);
         });
 
         it('should correct transform variables inside inner function body', () => {
@@ -166,72 +266,119 @@ describe('VariableDeclarationTransformer', () => {
         });
     });
 
-    describe('wrong replacement', () => {
-        it('shouldn\'t replace property node identifier', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/property-identifier.js'),
-                {
-                    ...NO_CUSTOM_NODES_PRESET
-                }
-            );
+    describe('variant #8: wrong replacement', () => {
+        describe('variant #1: property node identifier', () => {
+            const regExp: RegExp = /var _0x([a-f0-9]){4,6} *= *\{'test/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/property-identifier.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /var _0x([a-f0-9]){4,6} *= *\{'test/);
+            it('shouldn\'t replace property node identifier', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
 
-        it('shouldn\'t replace computed member expression identifier', () => {
-            let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-                readFileAsString(__dirname + '/fixtures/member-expression-identifier.js'),
+        describe('variant #2: computed member expression identifier', () => {
+            const regExp: RegExp = /_0x([a-f0-9]){4,6}\['test'\]/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/member-expression-identifier.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t replace computed member expression identifier', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+    });
+
+    describe('variant #9: object pattern as variable declarator', () => {
+        const objectPatternVariableDeclaratorRegExp: RegExp = /var *\{ *bar *\} *= *\{ *'bar' *: *'foo' *\};/;
+        const variableUsageRegExp: RegExp = /console\['log'\]\(bar\);/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/object-pattern.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
                 {
                     ...NO_CUSTOM_NODES_PRESET
                 }
             );
 
-            assert.match(obfuscationResult.getObfuscatedCode(),  /_0x([a-f0-9]){4,6}\['test'\]/);
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
         });
-    });
-
-    describe('object pattern as variable declarator', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/object-pattern.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
-        it('shouldn\'t transform object pattern variable declarator', () => {
-            const objectPatternVariableDeclaratorMatch: RegExp = /var *\{ *bar *\} *= *\{ *'bar' *: *'foo' *\};/;
-            const variableUsageMatch: RegExp = /console\['log'\]\(bar\);/;
+        it('match #1: shouldn\'t transform object pattern variable declarator', () => {
+            assert.match(obfuscatedCode, objectPatternVariableDeclaratorRegExp);
+        });
 
-            assert.match(obfuscatedCode, objectPatternVariableDeclaratorMatch);
-            assert.match(obfuscatedCode, variableUsageMatch);
+        it('match #2: shouldn\'t transform object pattern variable declarator', () => {
+            assert.match(obfuscatedCode, variableUsageRegExp);
         });
     });
 
-    describe('array pattern as variable declarator', () => {
-        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString(__dirname + '/fixtures/array-pattern.js'),
-            {
-                ...NO_CUSTOM_NODES_PRESET
-            }
-        );
-        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+    describe('variant #10: array pattern as variable declarator', () => {
+        const objectPatternVariableDeclaratorRegExp: RegExp = /var *\[ *(_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6}) *\] *= *\[0x1, *0x2\];/;
+        const variableUsageRegExp: RegExp = /console\['log'\]\((_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6})\);/;
 
-        const objectPatternVariableDeclaratorMatch: RegExp = /var *\[ *(_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6}) *\] *= *\[0x1, *0x2\];/;
-        const variableUsageMatch: RegExp = /console\['log'\]\((_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6})\);/;
+        let obfuscatedCode: string,
+            objectPatternIdentifierName1: string,
+            objectPatternIdentifierName2: string,
+            identifierName1: string,
+            identifierName2: string;
 
-        const objectPatternIdentifierName1: string = obfuscatedCode.match(objectPatternVariableDeclaratorMatch)![1];
-        const objectPatternIdentifierName2: string = obfuscatedCode.match(objectPatternVariableDeclaratorMatch)![2];
-        const identifierName1: string = obfuscatedCode.match(variableUsageMatch)![1];
-        const identifierName2: string = obfuscatedCode.match(variableUsageMatch)![2];
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/array-pattern.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
 
-        it('should transform array pattern variable declarator', () => {
-            assert.match(obfuscatedCode, objectPatternVariableDeclaratorMatch);
-            assert.match(obfuscatedCode, variableUsageMatch);
+            objectPatternIdentifierName1 = obfuscatedCode.match(objectPatternVariableDeclaratorRegExp)![1];
+            objectPatternIdentifierName2 = obfuscatedCode.match(objectPatternVariableDeclaratorRegExp)![2];
+            identifierName1 = obfuscatedCode.match(variableUsageRegExp)![1];
+            identifierName2 = obfuscatedCode.match(variableUsageRegExp)![2];
         });
 
-        it('should keep same identifier names same for identifiers in variable declaration and after variable declaration', () => {
+        it('match #1: should transform array pattern variable declarator', () => {
+            assert.match(obfuscatedCode, objectPatternVariableDeclaratorRegExp);
+        });
+
+        it('match #2: should transform array pattern variable declarator', () => {
+            assert.match(obfuscatedCode, variableUsageRegExp);
+        });
+
+        it('equal #1: should keep same identifier names same for identifiers in variable declaration and after variable declaration', () => {
             assert.equal(objectPatternIdentifierName1, identifierName1);
+        });
+
+        it('equal #2: should keep same identifier names same for identifiers in variable declaration and after variable declaration', () => {
             assert.equal(objectPatternIdentifierName2, identifierName2);
         });
     });

+ 296 - 267
test/functional-tests/stack-trace-analyzer/stack-trace-analyzer/StackTraceAnalyzer.spec.ts

@@ -152,308 +152,337 @@ describe('StackTraceAnalyzer', () => {
         const stackTraceAnalyzer: IStackTraceAnalyzer = inversifyContainerFacade
             .get<IStackTraceAnalyzer>(ServiceIdentifiers.IStackTraceAnalyzer);
 
-        let astTree: TNodeWithBlockStatement,
-            stackTraceData: IStackTraceData[],
-            expectedStackTraceData: IStackTraceData[];
-
-        it('should returns correct IStackTraceData - variant #1: basic-1', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/basic-1.js')
-                )
-            );
-
-            expectedStackTraceData = [
-                {
-                    name: 'baz',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
-                    stackTrace: []
-                },
-                {
-                    name: 'foo',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
-                    stackTrace: []
-                },
-                {
-                    name: 'bar',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
-                    stackTrace: [
-                        {
-                            name: 'inner2',
-                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner2')).body,
-                            stackTrace: [
-                                {
-                                    name: 'inner3',
-                                    callee: (<ESTree.FunctionExpression>getFunctionExpressionByName(astTree, 'inner3')).body,
-                                    stackTrace: []
-                                },
-                            ]
-                        },
-                        {
-                            name: 'inner1',
-                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
-                            stackTrace: []
-                        },
-                    ]
-                }
-            ];
-
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+        let expectedStackTraceData: IStackTraceData[],
+            stackTraceData: IStackTraceData[];
+
+        describe('variant #1: basic-1', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/basic-1.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
+
+                expectedStackTraceData = [
+                    {
+                        name: 'baz',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
+                        stackTrace: []
+                    },
+                    {
+                        name: 'foo',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
+                        stackTrace: []
+                    },
+                    {
+                        name: 'bar',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
+                        stackTrace: [
+                            {
+                                name: 'inner2',
+                                callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner2')).body,
+                                stackTrace: [
+                                    {
+                                        name: 'inner3',
+                                        callee: (<ESTree.FunctionExpression>getFunctionExpressionByName(astTree, 'inner3')).body,
+                                        stackTrace: []
+                                    },
+                                ]
+                            },
+                            {
+                                name: 'inner1',
+                                callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
+                                stackTrace: []
+                            },
+                        ]
+                    }
+                ];
+
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
+
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
 
-        it('should returns correct IStackTraceData - variant #2: basic-2', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/basic-2.js')
-                )
-            );
-
-            expectedStackTraceData = [
-                {
-                    name: 'bar',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
-                    stackTrace: []
-                },
-                {
-                    name: 'baz',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
-                    stackTrace: [
-                        {
-                            name: 'inner1',
-                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
-                            stackTrace: []
-                        },
-                    ]
-                },
-                {
-                    name: 'foo',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
-                    stackTrace: []
-                }
-            ];
-
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+        describe('variant #2: basic-2', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/basic-2.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
+
+                expectedStackTraceData = [
+                    {
+                        name: 'bar',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
+                        stackTrace: []
+                    },
+                    {
+                        name: 'baz',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
+                        stackTrace: [
+                            {
+                                name: 'inner1',
+                                callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
+                                stackTrace: []
+                            },
+                        ]
+                    },
+                    {
+                        name: 'foo',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
+                        stackTrace: []
+                    }
+                ];
+
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
+
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
 
-        it('should returns correct IStackTraceData - variant #3: deep conditions nesting', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/deep-conditions-nesting.js')
-                )
-            );
-
-            expectedStackTraceData = [
-                {
-                    name: 'bar',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
-                    stackTrace: []
-                },
-                {
-                    name: 'baz',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
-                    stackTrace: [
-                        {
-                            name: 'inner1',
-                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
-                            stackTrace: []
-                        },
-                    ]
-                },
-                {
-                    name: 'foo',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
-                    stackTrace: []
-                }
-            ];
-
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+        describe('variant #3: deep conditions nesting', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/deep-conditions-nesting.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
+
+                expectedStackTraceData = [
+                    {
+                        name: 'bar',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
+                        stackTrace: []
+                    },
+                    {
+                        name: 'baz',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
+                        stackTrace: [
+                            {
+                                name: 'inner1',
+                                callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
+                                stackTrace: []
+                            },
+                        ]
+                    },
+                    {
+                        name: 'foo',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
+                        stackTrace: []
+                    }
+                ];
+
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
+
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
 
-        it('should returns correct IStackTraceData - variant #4: call before declaration', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/call-before-declaration.js')
-                )
-            );
+        describe('variant #4: call before declaration', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/call-before-declaration.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
+
+                expectedStackTraceData = [
+                    {
+                        name: 'bar',
+                        callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
+                        stackTrace: []
+                    }
+                ];
+
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
+
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
+        });
 
-            expectedStackTraceData = [
-                {
-                    name: 'bar',
-                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
-                    stackTrace: []
-                }
-            ];
+        describe('variant #5: call expression of object member #1', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/call-expression-of-object-member-1.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
+
+                expectedStackTraceData = [
+                    {
+                        name: 'baz',
+                        callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'baz')).body,
+                        stackTrace: []
+                    },
+                    {
+                        name: 'baz',
+                        callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'baz')).body,
+                        stackTrace: []
+                    },
+                    {
+                        name: 'func',
+                        callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'func')).body,
+                        stackTrace: []
+                    },
+                    {
+                        name: 'bar',
+                        callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'bar')).body,
+                        stackTrace: [
+                            {
+                                name: 'inner1',
+                                callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
+                                stackTrace: [
+
+                                ]
+                            },
+                        ]
+                    },
+                    {
+                        name: 'bar',
+                        callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object', 'bar')).body,
+                        stackTrace: [
+                            {
+                                name: 'inner',
+                                callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner')).body,
+                                stackTrace: [
 
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                                ]
+                            },
+                        ]
+                    }
+                ];
 
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
-        });
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
 
-        it('should returns correct IStackTraceData - variant #5: call expression of object member #1', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/call-expression-of-object-member-1.js')
-                )
-            );
-
-            expectedStackTraceData = [
-                {
-                    name: 'baz',
-                    callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'baz')).body,
-                    stackTrace: []
-                },
-                {
-                    name: 'baz',
-                    callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'baz')).body,
-                    stackTrace: []
-                },
-                {
-                    name: 'func',
-                    callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'func')).body,
-                    stackTrace: []
-                },
-                {
-                    name: 'bar',
-                    callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'bar')).body,
-                    stackTrace: [
-                        {
-                            name: 'inner1',
-                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
-                            stackTrace: [
-
-                            ]
-                        },
-                    ]
-                },
-                {
-                    name: 'bar',
-                    callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object', 'bar')).body,
-                    stackTrace: [
-                        {
-                            name: 'inner',
-                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner')).body,
-                            stackTrace: [
-
-                            ]
-                        },
-                    ]
-                }
-            ];
-
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
 
-        it('should returns correct IStackTraceData - variant #5: call expression of object member #2', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/call-expression-of-object-member-2.js')
-                )
-            );
-
-            expectedStackTraceData = [
-                {
-                    name: 'baz',
-                    callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object', 'baz')).body,
-                    stackTrace: []
-                },
-                {
-                    name: 1,
-                    callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 1)).body,
-                    stackTrace: []
-                },
-            ];
-
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+        describe('variant #5: call expression of object member #2', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/call-expression-of-object-member-2.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
+
+                expectedStackTraceData = [
+                    {
+                        name: 'baz',
+                        callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object', 'baz')).body,
+                        stackTrace: []
+                    },
+                    {
+                        name: 1,
+                        callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 1)).body,
+                        stackTrace: []
+                    },
+                ];
+
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
+
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
 
-        it('should returns correct IStackTraceData - variant #6: no call expressions', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/no-call-expressions.js')
-                )
-            );
+        describe('variant #6: no call expressions', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/no-call-expressions.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
 
-            expectedStackTraceData = [];
+                expectedStackTraceData = [];
 
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
 
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
 
-        it('should returns correct IStackTraceData - variant #7: only call expression', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/only-call-expression.js')
-                )
-            );
+        describe('variant #7: only call expression', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/only-call-expression.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
 
-            expectedStackTraceData = [];
+                expectedStackTraceData = [];
 
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
 
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
 
-        it('should returns correct IStackTraceData - variant #8: self-invoking functions', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/self-invoking-functions.js')
-                )
-            );
+        describe('variant #8: self-invoking functions', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/self-invoking-functions.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
 
-            expectedStackTraceData = [
-                {
-                    name: null,
-                    callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'foo')).body,
-                    stackTrace: [{
+                expectedStackTraceData = [
+                    {
                         name: null,
-                        callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'bar')).body,
+                        callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'foo')).body,
                         stackTrace: [{
                             name: null,
-                            callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'baz')).body,
+                            callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'bar')).body,
                             stackTrace: [{
-                                name: 'inner',
-                                callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner')).body,
-                                stackTrace: []
+                                name: null,
+                                callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'baz')).body,
+                                stackTrace: [{
+                                    name: 'inner',
+                                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner')).body,
+                                    stackTrace: []
+                                }]
                             }]
                         }]
-                    }]
-                }
-            ];
+                    }
+                ];
 
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
 
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
 
-        it('should returns correct IStackTraceData - variant #9: no recursion', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/no-recursion.js')
-                )
-            );
-
-            expectedStackTraceData = [
-                {
-                    name: 'bar',
-                    callee: (<ESTree.FunctionExpression>getFunctionExpressionByName(astTree, 'bar')).body,
-                    stackTrace: []
-                }
-            ];
-
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-
-            assert.deepEqual(stackTraceData, expectedStackTraceData);
+        describe('variant #9: no recursion', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/no-recursion.js');
+                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                    NodeUtils.convertCodeToStructure(code)
+                );
+
+                expectedStackTraceData = [
+                    {
+                        name: 'bar',
+                        callee: (<ESTree.FunctionExpression>getFunctionExpressionByName(astTree, 'bar')).body,
+                        stackTrace: []
+                    }
+                ];
+
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+            });
+
+            it('should return correct stack trace data', () => {
+                assert.deepEqual(stackTraceData, expectedStackTraceData);
+            });
         });
     });
 });

+ 72 - 49
test/functional-tests/templates/custom-nodes/domain-lock-nodes/DomainLockNodeTemplate.spec.ts

@@ -13,7 +13,7 @@ import { CryptUtils } from '../../../../../src/utils/CryptUtils';
  * @returns {Function}
  */
 function getFunctionFromTemplate (templateData: any, callsControllerFunctionName: string,  currentDomain: string) {
-    let domainLockTemplate: string = format(DomainLockNodeTemplate(), templateData);
+    const domainLockTemplate: string = format(DomainLockNodeTemplate(), templateData);
 
     return Function(`
         document = {
@@ -33,57 +33,80 @@ function getFunctionFromTemplate (templateData: any, callsControllerFunctionName
 }
 
 describe('DomainLockNodeTemplate (): string', () => {
-    let domainsString: string,
-        currentDomain: string,
-        hiddenDomainsString: string,
-        diff: string,
-        singleNodeCallControllerFunctionName: string = 'callsController';
-
-    it('should correctly runs code inside template if current domain matches with `domainsString`', () => {
-        domainsString = ['www.example.com'].join(';');
-        currentDomain = 'www.example.com';
-        [
-            hiddenDomainsString,
-            diff
-        ] = CryptUtils.hideString(domainsString, domainsString.length * 3);
-
-        assert.doesNotThrow(() => getFunctionFromTemplate({
-            domainLockFunctionName: 'domainLockFunction',
-            diff: diff,
-            domains: hiddenDomainsString,
-            singleNodeCallControllerFunctionName
-        }, singleNodeCallControllerFunctionName, currentDomain));
+    const singleNodeCallControllerFunctionName: string = 'callsController';
+
+    describe('variant #1: current domain matches with `domainsString`', () => {
+        const domainsString: string = ['www.example.com'].join(';');
+        const currentDomain: string = 'www.example.com';
+
+        let testFunc: () => void;
+
+        before(() => {
+            const [
+                hiddenDomainsString,
+                diff
+            ] = CryptUtils.hideString(domainsString, domainsString.length * 3);
+
+            testFunc = () => getFunctionFromTemplate({
+                domainLockFunctionName: 'domainLockFunction',
+                diff: diff,
+                domains: hiddenDomainsString,
+                singleNodeCallControllerFunctionName
+            }, singleNodeCallControllerFunctionName, currentDomain);
+        });
+
+        it('should correctly runs code inside template', () => {
+            assert.doesNotThrow(testFunc);
+        });
     });
 
-    it('should correctly runs code inside template if current domain matches with base domain of `domainsString` item', () => {
-        domainsString = ['www.test.com', '.example.com'].join(';');
-        currentDomain = 'subdomain.example.com';
-        [
-            hiddenDomainsString,
-            diff
-        ] = CryptUtils.hideString(domainsString, domainsString.length * 3);
-
-        assert.doesNotThrow(() => getFunctionFromTemplate({
-            domainLockFunctionName: 'domainLockFunction',
-            diff: diff,
-            domains: hiddenDomainsString,
-            singleNodeCallControllerFunctionName
-        }, singleNodeCallControllerFunctionName, currentDomain));
+    describe('variant #2: urrent domain matches with base domain of `domainsString` item', () => {
+        const domainsString: string = ['www.test.com', '.example.com'].join(';');
+        const currentDomain: string = 'subdomain.example.com';
+
+        let testFunc: () => void;
+
+        before(() => {
+            const [
+                hiddenDomainsString,
+                diff
+            ] = CryptUtils.hideString(domainsString, domainsString.length * 3);
+
+            testFunc = () => getFunctionFromTemplate({
+                domainLockFunctionName: 'domainLockFunction',
+                diff: diff,
+                domains: hiddenDomainsString,
+                singleNodeCallControllerFunctionName
+            }, singleNodeCallControllerFunctionName, currentDomain);
+        });
+
+        it('should correctly runs code inside template', () => {
+            assert.doesNotThrow(testFunc);
+        });
     });
 
-    it('should throw an error if current domain doesn\'t match with `domainsString`', () => {
-        domainsString = ['www.example.com'].join(';');
-        currentDomain = 'www.test.com';
-        [
-            hiddenDomainsString,
-            diff
-        ] = CryptUtils.hideString(domainsString, domainsString.length * 3);
-
-        assert.throws(() => getFunctionFromTemplate({
-            domainLockFunctionName: 'domainLockFunction',
-            diff: diff,
-            domains: hiddenDomainsString,
-            singleNodeCallControllerFunctionName
-        }, singleNodeCallControllerFunctionName, currentDomain));
+    describe('variant #3: current domain doesn\'t match with `domainsString`', () => {
+        const domainsString: string = ['www.example.com'].join(';');
+        const currentDomain: string = 'www.test.com';
+
+        let testFunc: () => void;
+
+        before(() => {
+            const [
+                hiddenDomainsString,
+                diff
+            ] = CryptUtils.hideString(domainsString, domainsString.length * 3);
+
+            testFunc = () => getFunctionFromTemplate({
+                domainLockFunctionName: 'domainLockFunction',
+                diff: diff,
+                domains: hiddenDomainsString,
+                singleNodeCallControllerFunctionName
+            }, singleNodeCallControllerFunctionName, currentDomain);
+        });
+
+        it('should throw an error', () => {
+            assert.throws(testFunc);
+        });
     });
 });

+ 43 - 20
test/functional-tests/templates/custom-nodes/string-array-nodes/StringArrayCallsWrapperNodeTemplate.spec.ts

@@ -23,7 +23,7 @@ function getFunctionFromTemplateBase64Encoding (
     stringArrayCallsWrapperName: string,
     index: string
 ) {
-    let stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), templateData);
+    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), templateData);
 
     return Function(`
         var ${stringArrayName} = ['${CryptUtils.btoa('test1')}'];
@@ -49,7 +49,7 @@ function getFunctionFromTemplateRc4Encoding (
     index: string,
     key: string
 ) {
-    let stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), templateData);
+    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), templateData);
 
     return Function(`
         var ${stringArrayName} = ['${CryptUtils.btoa(CryptUtils.rc4('test1', key))}'];
@@ -61,33 +61,56 @@ function getFunctionFromTemplateRc4Encoding (
 }
 
 describe('StringArrayCallsWrapperNodeTemplate (): string', () => {
-    let stringArrayName: string = 'stringArrayName',
-        stringArrayCallsWrapperName: string = 'stringArrayCallsWrapperName',
-        atobDecodeNodeTemplate: string = format(StringArrayBase64DecodeNodeTemplate(), {
+    const stringArrayName: string = 'stringArrayName';
+    const stringArrayCallsWrapperName: string = 'stringArrayCallsWrapperName';
+
+    describe('variant #1: `base64` encoding', () => {
+        const atobDecodeNodeTemplate: string = format(StringArrayBase64DecodeNodeTemplate(), {
             atobPolyfill: AtobTemplate(),
             selfDefendingCode: '',
             stringArrayCallsWrapperName
-        }),
-        rc4DecodeNodeTemplate: string = format(StringArrayRc4DecodeNodeTemplate(), {
+        });
+        const index: string = '0x0';
+        const expectedDecodedValue: string = 'test1';
+
+        let decodedValue: string;
+
+        before(() => {
+            decodedValue = getFunctionFromTemplateBase64Encoding({
+                decodeNodeTemplate: atobDecodeNodeTemplate,
+                stringArrayCallsWrapperName,
+                stringArrayName
+            }, stringArrayName, stringArrayCallsWrapperName, index);
+        });
+
+        it('should correctly return decoded value', () => {
+            assert.deepEqual(decodedValue, expectedDecodedValue);
+        });
+    });
+
+    describe('variant #2: `rc4` encoding', () => {
+        const rc4DecodeNodeTemplate: string = format(StringArrayRc4DecodeNodeTemplate(), {
             atobPolyfill: AtobTemplate(),
             rc4Polyfill: Rc4Template(),
             selfDefendingCode: '',
             stringArrayCallsWrapperName
         });
+        const index: string = '0x0';
+        const key: string = 'key';
+        const expectedDecodedValue: string = 'test1';
 
-    it('should correctly returns decoded value with base64 encoding', () => {
-        assert.deepEqual(getFunctionFromTemplateBase64Encoding({
-            decodeNodeTemplate: atobDecodeNodeTemplate,
-            stringArrayCallsWrapperName,
-            stringArrayName
-        }, stringArrayName, stringArrayCallsWrapperName, '0x0'), 'test1');
-    });
+        let decodedValue: string;
 
-    it('should correctly returns decoded value with rc4 encoding', () => {
-        assert.deepEqual(getFunctionFromTemplateRc4Encoding({
-            decodeNodeTemplate: rc4DecodeNodeTemplate,
-            stringArrayCallsWrapperName,
-            stringArrayName
-        }, stringArrayName, stringArrayCallsWrapperName, '0x0', 'key'), 'test1');
+        before(() => {
+            decodedValue = getFunctionFromTemplateRc4Encoding({
+                decodeNodeTemplate: rc4DecodeNodeTemplate,
+                stringArrayCallsWrapperName,
+                stringArrayName
+            }, stringArrayName, stringArrayCallsWrapperName, index, key);
+        });
+
+        it('should correctly return decoded value', () => {
+            assert.deepEqual(decodedValue, expectedDecodedValue);
+        });
     });
 });

+ 5 - 7
test/performance-tests/JavaScriptObfuscatorPerformance.spec.ts

@@ -4,13 +4,13 @@ import { readFileAsString } from '../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscator';
 
-describe('JavaScriptObfuscator performance', () => {
+describe('JavaScriptObfuscator performance', function () {
     const iterationsCount: number = 500;
 
-    describe('performance: multiple calls', () => {
-        it('shows performance time with multiple obfuscator calls', function (): void {
-            this.timeout(100000);
+    this.timeout(100000);
 
+    describe('performance: multiple calls', () => {
+        it('shows performance time with multiple obfuscator calls', () => {
             for (let i: number = 0; i < iterationsCount; i++) {
                 JavaScriptObfuscator.obfuscate(readFileAsString('./test/fixtures/sample.js'));
             }
@@ -20,9 +20,7 @@ describe('JavaScriptObfuscator performance', () => {
     });
 
     describe('performance: large source code', () => {
-        it('shows performance time with large code size', function (): void {
-            this.timeout(100000);
-
+        it('shows performance time with large code size', () => {
             JavaScriptObfuscator.obfuscate(readFileAsString('./test/fixtures/sample.js').repeat(iterationsCount));
 
             assert.isOk(true);

+ 12 - 5
test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

@@ -6,11 +6,14 @@ import { readFileAsString } from '../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscator';
 
-describe('JavaScriptObfuscator runtime eval', () => {
-    it('should obfuscate code without any runtime errors after obfuscation: variant #1 sha256', () => {
-        const code: string = readFileAsString(__dirname + '/fixtures/sha256.js');
+describe('JavaScriptObfuscator runtime eval', function () {
+    this.timeout(100000);
 
-        const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+    let obfuscatedCode: string;
+
+    before(() => {
+        const code: string = readFileAsString(__dirname + '/fixtures/sha256.js');
+        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             code,
             {
                 controlFlowFlattening: true,
@@ -21,8 +24,12 @@ describe('JavaScriptObfuscator runtime eval', () => {
             }
         );
 
+        obfuscatedCode = obfuscationResult.getObfuscatedCode();
+    });
+
+    it('should obfuscate code without any runtime errors after obfuscation: variant #1 sha256', () => {
         assert.equal(
-            eval(`${obfuscationResult1.getObfuscatedCode()} sha256('test');`),
+            eval(`${obfuscatedCode} sha256('test');`),
             '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
         );
     });

+ 38 - 22
test/unit-tests/cli/cli-utils/CLIUtils.spec.ts

@@ -3,11 +3,11 @@ import * as mkdirp from 'mkdirp';
 
 import { assert } from 'chai';
 
-import { CLIUtils } from '../../../../src/cli/CLIUtils';
+import { CLIUtils } from '../../../../src/cli/utils/CLIUtils';
 
 describe('CLIUtils', () => {
-    const fileContent: string = 'test',
-        tmpDir: string = 'test/tmp';
+    const fileContent: string = 'test';
+    const tmpDir: string = 'test/tmp';
 
     before(() => {
         mkdirp.sync(tmpDir);
@@ -37,41 +37,57 @@ describe('CLIUtils', () => {
     });
 
     describe('getPackageConfig (): IPackageConfig', () => {
-        it('should return `package.json` content for current CLI program as object', () => {
+        it('should return `package.json` `name` field for current CLI program as object', () => {
             assert.property(CLIUtils.getPackageConfig(), 'name');
+        });
+
+        it('should return `package.json` `version` field for current CLI program as object', () => {
             assert.property(CLIUtils.getPackageConfig(), 'version');
         });
     });
 
     describe('validateInputPath (inputPath: string): void', () => {
-        let inputPath: string,
-            tmpFileName: string;
+        describe('`inputPath` is a valid path', () => {
+            const tmpFileName: string = 'test.js';
+            const inputPath: string = `${tmpDir}/${tmpFileName}`;
 
-        it('shouldn\'t throw an error if `inputPath` is a valid path', () => {
-            tmpFileName = 'test.js';
-            inputPath = `${tmpDir}/${tmpFileName}`;
-            fs.writeFileSync(inputPath, fileContent);
+            before(() => {
+                fs.writeFileSync(inputPath, fileContent);
+            });
 
-            assert.doesNotThrow(() => CLIUtils.validateInputPath(inputPath), ReferenceError);
+            it('shouldn\'t throw an error if `inputPath` is a valid path', () => {
+                assert.doesNotThrow(() => CLIUtils.validateInputPath(inputPath), ReferenceError);
+            });
 
-            fs.unlinkSync(inputPath);
+            after(() => {
+                fs.unlinkSync(inputPath);
+            });
         });
 
-        it('should throw an error if `inputPath` is not a valid path', () => {
-            tmpFileName = 'test.js';
-            inputPath = `${tmpDir}/${tmpFileName}`;
+        describe('`inputPath` is not a valid path', () => {
+            const tmpFileName: string = 'test.js';
+            const inputPath: string = `${tmpDir}/${tmpFileName}`;
 
-            assert.throws(() => CLIUtils.validateInputPath(inputPath), ReferenceError);
+            it('should throw an error if `inputPath` is not a valid path', () => {
+                assert.throws(() => CLIUtils.validateInputPath(inputPath), ReferenceError);
+            });
         });
 
-        it('should throw an error if `inputPath` is a file name has invalid extension', () => {
-            tmpFileName = 'test.ts';
-            inputPath = `${tmpDir}/${tmpFileName}`;
-            fs.writeFileSync(inputPath, fileContent);
+        describe('`inputPath` is a file name has invalid extension', () => {
+            const tmpFileName: string = 'test.ts';
+            const inputPath: string = `${tmpDir}/${tmpFileName}`;
+
+            before(() => {
+                fs.writeFileSync(inputPath, fileContent);
+            });
 
-            assert.throws(() => CLIUtils.validateInputPath(inputPath), ReferenceError);
+            it('should throw an error if `inputPath` is a file name has invalid extension', () => {
+                assert.throws(() => CLIUtils.validateInputPath(inputPath), ReferenceError);
+            });
 
-            fs.unlinkSync(inputPath);
+            after(() => {
+                fs.unlinkSync(inputPath);
+            });
         });
     });
 

+ 28 - 12
test/unit-tests/decorators/initializable/Initializable.spec.ts

@@ -5,8 +5,8 @@ import { IInitializable } from '../../../../src/interfaces/IInitializable';
 
 describe('@initializable', () => {
     describe('initializable (initializeMethodKey: string): any', () => {
-        it('shouldn\'t throws an errors if property was initialized', () => {
-            assert.doesNotThrow(() => {
+        describe('variant #1: property was initialized', () => {
+            const testFunc: () => void = () => {
                 class Foo implements IInitializable {
                     @initializable()
                     public property: string;
@@ -21,11 +21,15 @@ describe('@initializable', () => {
                 foo.initialize('baz');
 
                 foo.property;
-            }, Error);
+            };
+
+            it('shouldn\'t throws an errors if property was initialized', () => {
+                assert.doesNotThrow(testFunc, Error);
+            });
         });
 
-        it('shouldn\'t throws an errors if custom initialization method name is passed', () => {
-            assert.doesNotThrow(() => {
+        describe('variant #2: custom initialization method name is passed', () => {
+            const testFunc: () => void = () => {
                 class Foo implements IInitializable {
                     @initializable()
                     public property: string;
@@ -43,11 +47,15 @@ describe('@initializable', () => {
                 foo.bar('baz');
 
                 foo.property;
-            }, Error);
+            };
+
+            it('shouldn\'t throws an errors if custom initialization method name is passed', () => {
+                assert.doesNotThrow(testFunc, Error);
+            });
         });
 
-        it('should throws an error if property didn\'t initialized', () => {
-            assert.throws(() => {
+        describe('variant #3: property didn\'t initialized', () => {
+            const testFunc: () => void = () => {
                 class Foo implements IInitializable {
                     @initializable()
                     public property: string;
@@ -61,11 +69,15 @@ describe('@initializable', () => {
                 foo.initialize('baz');
 
                 foo.property;
-            }, /Property `property` is not initialized/);
+            };
+
+            it('should throws an error if property didn\'t initialized', () => {
+                assert.throws(testFunc, /Property `property` is not initialized/);
+            });
         });
 
-        it('should throws an error if `initialize` method with custom name wasn\'t found', () => {
-            assert.throws(() => {
+        describe('variant #4: `initialize` method with custom name', () => {
+            const testFunc: () => void = () => {
                 class Foo {
                     @initializable('bar')
                     public property: string;
@@ -80,7 +92,11 @@ describe('@initializable', () => {
                 foo.initialize('baz');
 
                 foo.property;
-            }, /method with initialization logic not found/);
+            };
+
+            it('should throws an error if `initialize` method with custom name wasn\'t found', () => {
+                assert.throws(testFunc, /method with initialization logic not found/);
+            });
         });
     });
 });

+ 87 - 124
test/unit-tests/node/node-appender/NodeAppender.spec.ts

@@ -17,28 +17,34 @@ import { NodeAppender } from '../../../../src/node/NodeAppender';
 import { Nodes } from '../../../../src/node/Nodes';
 import { NodeUtils } from '../../../../src/node/NodeUtils';
 
+/**
+ * @param fixturePath
+ * @return {TStatement[]}
+ */
+const convertCodeToStructure: (fixturePath: string) => TStatement[] = (fixturePath) => {
+    return NodeUtils.convertCodeToStructure(
+        readFileAsString(`${__dirname}${fixturePath}`)
+    );
+};
+
+/**
+ * @param fixturePath
+ * @return {ESTree.Program}
+ */
+const convertCodeToAst: (fixturePath: string) => ESTree.Program = (fixturePath) => {
+    return Nodes.getProgramNode(convertCodeToStructure(fixturePath));
+};
+
 describe('NodeAppender', () => {
     describe('appendNode (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[]): void', () => {
         let astTree: ESTree.Program,
             expectedAstTree: ESTree.Program,
             node: TStatement[];
 
-        beforeEach(() => {
-            node = NodeUtils.convertCodeToStructure(
-                readFileAsString(__dirname + '/fixtures/simple-input.js')
-            );
-
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/append-node.js')
-                )
-            );
-
-            expectedAstTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/append-node-expected.js')
-                )
-            );
+        before(() => {
+            node = convertCodeToStructure('/fixtures/simple-input.js');
+            astTree = convertCodeToAst('/fixtures/append-node.js');
+            expectedAstTree = convertCodeToAst('/fixtures/append-node-expected.js');
 
             astTree = NodeUtils.parentize(astTree);
             expectedAstTree = NodeUtils.parentize(expectedAstTree);
@@ -62,116 +68,97 @@ describe('NodeAppender', () => {
             stackTraceData: IStackTraceData[];
 
         beforeEach(() => {
-            node = NodeUtils.convertCodeToStructure(
-                readFileAsString(__dirname + '/fixtures/simple-input.js')
-            );
+            node = convertCodeToStructure('/fixtures/simple-input.js');
         });
 
-        it('should append node into first and deepest function call in calls trace - variant #1', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/variant-1.js')
-                )
-            );
-
-            expectedAstTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/variant-1-expected.js')
-                )
-            );
+        describe('variant #1: nested function calls', () => {
+            beforeEach(() => {
+                astTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/variant-1.js');
+                expectedAstTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/variant-1-expected.js');
 
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-            NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node);
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node);
+            });
 
-            assert.deepEqual(astTree, expectedAstTree);
+            it('should append node into first and deepest function call in nested function calls', () => {
+                assert.deepEqual(astTree, expectedAstTree);
+            });
         });
 
-        it('should append node into first and deepest function call in calls trace - variant #2', () => {
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/variant-2.js')
-                )
-            );
+        describe('variant #2: nested function calls', () => {
+            beforeEach(() => {
+                astTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/variant-2.js');
+                expectedAstTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/variant-2-expected.js');
 
-            expectedAstTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/variant-2-expected.js')
-                )
-            );
+                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node);
 
-            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-            NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node);
+            });
 
-            assert.deepEqual(astTree, expectedAstTree);
+            it('should append node into first and deepest function call in nested function calls', () => {
+                assert.deepEqual(astTree, expectedAstTree);
+            });
         });
 
         describe('append by specific index', () => {
             let astTree: ESTree.Program;
 
             beforeEach(() => {
-                astTree = Nodes.getProgramNode(
-                    NodeUtils.convertCodeToStructure(
-                        readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/by-index.js')
-
-                    )
-                );
+                astTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/by-index.js');
             });
 
-            it('should append node into deepest function call by specified index in calls trace - variant #1', () => {
-                expectedAstTree = Nodes.getProgramNode(
-                    NodeUtils.convertCodeToStructure(
-                        readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/by-index-variant-1-expected.js')
+            describe('variant #1: append by specific index in nested function calls', () => {
+                beforeEach(() => {
+                    expectedAstTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/by-index-variant-1-expected.js');
 
-                    )
-                );
+                    stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                    NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node, 2);
 
-                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-                NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node, 2);
+                });
 
-                assert.deepEqual(astTree, expectedAstTree);
+                it('should append node into deepest function call by specified index in nested function calls', () => {
+                    assert.deepEqual(astTree, expectedAstTree);
+                });
             });
 
-            it('should append node into deepest function call by specified index in calls trace - variant #2', () => {
-                expectedAstTree = Nodes.getProgramNode(
-                    NodeUtils.convertCodeToStructure(
-                        readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/by-index-variant-2-expected.js')
+            describe('variant #2: append by specific index in nested function calls', () => {
+                beforeEach(() => {
+                    expectedAstTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/by-index-variant-2-expected.js');
 
-                    )
-                );
+                    stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                    NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node, 1);
 
-                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-                NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node, 1);
+                });
 
-                assert.deepEqual(astTree, expectedAstTree);
+                it('should append node into deepest function call by specified index in nested function calls', () => {
+                    assert.deepEqual(astTree, expectedAstTree);
+                });
             });
 
-            it('should append node into deepest function call by specified index in calls trace - variant #3', () => {
-                astTree = Nodes.getProgramNode(
-                    NodeUtils.convertCodeToStructure(
-                        readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/by-index-variant-3.js')
-                    )
-                );
-                expectedAstTree = Nodes.getProgramNode(
-                    NodeUtils.convertCodeToStructure(
-                        readFileAsString(__dirname + '/fixtures/append-node-to-optimal-block-scope/by-index-variant-3-expected.js')
-                    )
-                );
+            describe('variant #3: append by specific index in nested function calls', () => {
+                beforeEach(() => {
+                    astTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/by-index-variant-3.js');
+                    expectedAstTree = convertCodeToAst('/fixtures/append-node-to-optimal-block-scope/by-index-variant-3-expected.js');
 
-                stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
-                NodeAppender.appendNodeToOptimalBlockScope(
-                    stackTraceData,
-                    astTree,
-                    node,
-                    NodeAppender.getRandomStackTraceIndex(stackTraceData.length)
-                );
+                    stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
+                    NodeAppender.appendNodeToOptimalBlockScope(
+                        stackTraceData,
+                        astTree,
+                        node,
+                        NodeAppender.getRandomStackTraceIndex(stackTraceData.length)
+                    );
 
-                assert.deepEqual(astTree, expectedAstTree);
+                });
+
+                it('should append node into deepest function call by specified index in nested function calls', () => {
+                    assert.deepEqual(astTree, expectedAstTree);
+                });
             });
         });
     });
 
     describe('getRandomStackTraceIndex (stackTraceRootLength: number): number', () => {
-        it('should returns random index between 0 and stack trace data root length', () => {
+        it('should return random index between 0 and stack trace data root length', () => {
             let index: number;
 
             for (let i: number = 0; i < 100; i++) {
@@ -188,22 +175,10 @@ describe('NodeAppender', () => {
             expectedAstTree: ESTree.Program,
             node: TStatement[];
 
-        beforeEach(() => {
-            node = NodeUtils.convertCodeToStructure(
-                readFileAsString(__dirname + '/fixtures/simple-input.js')
-            );
-
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/insert-node-at-index.js')
-                )
-            );
-
-            expectedAstTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/insert-node-at-index-expected.js')
-                )
-            );
+        before(() => {
+            node = convertCodeToStructure('/fixtures/simple-input.js');
+            astTree = convertCodeToAst('/fixtures/insert-node-at-index.js');
+            expectedAstTree = convertCodeToAst('/fixtures/insert-node-at-index-expected.js');
 
             astTree = NodeUtils.parentize(astTree);
             expectedAstTree = NodeUtils.parentize(expectedAstTree);
@@ -221,22 +196,10 @@ describe('NodeAppender', () => {
             expectedAstTree: ESTree.Program,
             node: TStatement[];
 
-        beforeEach(() => {
-            node = NodeUtils.convertCodeToStructure(
-                readFileAsString(__dirname + '/fixtures/simple-input.js')
-            );
-
-            astTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/prepend-node.js')
-                )
-            );
-
-            expectedAstTree = Nodes.getProgramNode(
-                NodeUtils.convertCodeToStructure(
-                    readFileAsString(__dirname + '/fixtures/prepend-node-expected.js')
-                )
-            );
+        before(() => {
+            node = convertCodeToStructure('/fixtures/simple-input.js');
+            astTree = convertCodeToAst('/fixtures/prepend-node.js');
+            expectedAstTree = convertCodeToAst('/fixtures/prepend-node-expected.js');
 
             astTree = NodeUtils.parentize(astTree);
             expectedAstTree = NodeUtils.parentize(expectedAstTree);

+ 174 - 86
test/unit-tests/node/node-utils/NodeUtils.spec.ts

@@ -2,6 +2,8 @@ import * as ESTree from 'estree';
 
 import { assert } from 'chai';
 
+import { TStatement } from '../../../../src/types/node/TStatement';
+
 import { Nodes } from '../../../../src/node/Nodes';
 import { NodeUtils } from '../../../../src/node/NodeUtils';
 
@@ -10,7 +12,7 @@ describe('NodeUtils', () => {
         let literalNode: any,
             expectedLiteralNode: any;
 
-        beforeEach(() => {
+        before(() => {
             literalNode = Nodes.getLiteralNode('value');
             delete literalNode['x-verbatim-property'];
 
@@ -25,86 +27,71 @@ describe('NodeUtils', () => {
     });
 
     describe('clone <T extends ESTree.Node> (astTree: T): T', () => {
-        let ifStatementNode1: ESTree.IfStatement,
-            ifStatementNode2: ESTree.IfStatement,
-            ifStatementBlockStatementNode1: ESTree.BlockStatement,
-            ifStatementBlockStatementNode2: ESTree.BlockStatement,
-            expressionStatementNode1: ESTree.ExpressionStatement,
-            expressionStatementNode2: ESTree.ExpressionStatement,
-            expressionStatementNode3: ESTree.ExpressionStatement,
-            expressionStatementNode4: ESTree.ExpressionStatement,
-            programNode1: ESTree.Program,
-            programNode2: ESTree.Program;
+        let programNode: ESTree.Program,
+            expectedProgramNode: ESTree.Program;
 
-        beforeEach(() => {
+        before(() => {
             // actual AST tree
-            expressionStatementNode1 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
-            expressionStatementNode2 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
+            const expressionStatementNode1: ESTree.ExpressionStatement = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
+            const expressionStatementNode2: ESTree.ExpressionStatement = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
 
-            ifStatementBlockStatementNode1 = Nodes.getBlockStatementNode([
+            const ifStatementBlockStatementNode1: ESTree.BlockStatement = Nodes.getBlockStatementNode([
                 expressionStatementNode1,
                 expressionStatementNode2
             ]);
 
-            ifStatementNode1 = Nodes.getIfStatementNode(
+            const ifStatementNode1: ESTree.IfStatement = Nodes.getIfStatementNode(
                 Nodes.getLiteralNode(true),
                 ifStatementBlockStatementNode1
             );
 
-            programNode1 = Nodes.getProgramNode([
-                ifStatementNode1
-            ]);
-
             // expected AST tree
-            expressionStatementNode3 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
-            expressionStatementNode4 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
+            const expressionStatementNode3: ESTree.ExpressionStatement = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
+            const expressionStatementNode4: ESTree.ExpressionStatement = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
 
-            ifStatementBlockStatementNode2 = Nodes.getBlockStatementNode([
+            const ifStatementBlockStatementNode2: ESTree.BlockStatement = Nodes.getBlockStatementNode([
                 expressionStatementNode3,
                 expressionStatementNode4
             ]);
 
-            ifStatementNode2 = Nodes.getIfStatementNode(
+            const ifStatementNode2: ESTree.IfStatement = Nodes.getIfStatementNode(
                 Nodes.getLiteralNode(true),
                 ifStatementBlockStatementNode2
             );
 
-            programNode2 = Nodes.getProgramNode([
-                ifStatementNode2
-            ]);
-
-            programNode2 = NodeUtils.parentize(programNode2);
+            programNode = NodeUtils.clone(
+                Nodes.getProgramNode([
+                    ifStatementNode1
+                ])
+            );
+            expectedProgramNode = NodeUtils.parentize(
+                Nodes.getProgramNode([
+                    ifStatementNode2
+                ])
+            );
         });
 
         it('should clone given AST-tree', () => {
-            assert.deepEqual(NodeUtils.clone(programNode1), programNode2);
+            assert.deepEqual(programNode, expectedProgramNode);
         });
     });
 
     describe('convertCodeToStructure (code: string): ESTree.Node[]', () => {
-        let code: string,
-            identifierNode: ESTree.Identifier,
-            literalNode: ESTree.Literal,
-            programNode: ESTree.Program,
-            variableDeclarationNode: ESTree.VariableDeclaration,
-            variableDeclaratorNode: ESTree.VariableDeclarator;
+        let structure: TStatement[],
+            expectedStructure: TStatement[];
 
-        beforeEach(() => {
-            code = `
+        before(() => {
+            const code: string = `
                 var abc = 'cde';
             `;
 
-            identifierNode = Nodes.getIdentifierNode('abc');
-
-            literalNode = Nodes.getLiteralNode('cde');
-
-            variableDeclaratorNode = Nodes.getVariableDeclaratorNode(identifierNode, literalNode);
-
-            variableDeclarationNode = Nodes.getVariableDeclarationNode([
+            const identifierNode: ESTree.Identifier = Nodes.getIdentifierNode('abc');
+            const literalNode: ESTree.Literal = Nodes.getLiteralNode('cde');
+            const variableDeclaratorNode: ESTree.VariableDeclarator = Nodes.getVariableDeclaratorNode(identifierNode, literalNode);
+            const variableDeclarationNode: ESTree.VariableDeclaration = Nodes.getVariableDeclarationNode([
                 variableDeclaratorNode
             ]);
-
-            programNode = Nodes.getProgramNode([
+            const programNode: ESTree.Program = Nodes.getProgramNode([
                 variableDeclarationNode
             ]);
 
@@ -113,10 +100,13 @@ describe('NodeUtils', () => {
             variableDeclaratorNode.parentNode = variableDeclarationNode;
             identifierNode.parentNode = variableDeclaratorNode;
             literalNode.parentNode = variableDeclaratorNode;
+
+            structure = NodeUtils.convertCodeToStructure(code);
+            expectedStructure = [variableDeclarationNode];
         });
 
         it('should convert code to `ESTree.Node[]` structure array', () => {
-            assert.deepEqual(NodeUtils.convertCodeToStructure(code), [variableDeclarationNode]);
+            assert.deepEqual(structure, expectedStructure);
         });
     });
 
@@ -124,7 +114,7 @@ describe('NodeUtils', () => {
         let structure: ESTree.Node[],
             expectedCode: string;
 
-        beforeEach(() => {
+        before(() => {
             structure = [
                 Nodes.getProgramNode([
                     Nodes.getVariableDeclarationNode([
@@ -160,6 +150,9 @@ describe('NodeUtils', () => {
 
         it('should return block-statement child node of given node if that node has block-statement', () => {
             assert.deepEqual(NodeUtils.getBlockStatementNodeByIndex(blockStatementNode), expressionStatementNode1);
+        });
+
+        it('should return block-statement child node of given node with index `1` if that node has block-statement', () => {
             assert.deepEqual(NodeUtils.getBlockStatementNodeByIndex(blockStatementNode, 1), expressionStatementNode2);
         });
 
@@ -184,7 +177,7 @@ describe('NodeUtils', () => {
             functionDeclarationNode: ESTree.FunctionDeclaration,
             programNode: ESTree.Program;
 
-        beforeEach(() => {
+        before(() => {
             expressionStatementNode1 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
             expressionStatementNode2 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
             expressionStatementNode3 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
@@ -230,19 +223,55 @@ describe('NodeUtils', () => {
             expressionStatementNode3.parentNode = ifStatementBlockStatementNode2;
         });
 
-        it('should return block-scope node for given node', () => {
+        it('should return block-scope node for `program` node child', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(programNode)[0], programNode);
+        });
+
+        it('should return block-scope node for `functionDeclaration` node child node #1', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(functionDeclarationNode)[0], programNode);
+        });
+
+        it('should return block-scope node for `functionDeclaration blockStatement` node child node #1', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(functionDeclarationBlockStatementNode)[0], programNode);
+        });
+
+        it('should return block-scope node for `expressionStatement` node #1 child node #1', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(expressionStatementNode1)[0], functionDeclarationBlockStatementNode);
+        });
+
+        it('should return block-scope node for `expressionStatement` node #1 child node #2', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(expressionStatementNode1)[1], programNode);
+        });
+
+        it('should return block-scope node for `ifStatement` node child node #1', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(ifStatementNode1)[0], functionDeclarationBlockStatementNode);
+        });
+
+        it('should return block-scope node for `ifStatement` node child node #2', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(ifStatementNode1)[1], programNode);
+        });
+
+        it('should return block-scope node for `ifStatement blockStatement` node #1 child node #1', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(ifStatementBlockStatementNode1)[0], functionDeclarationBlockStatementNode);
+        });
+
+        it('should return block-scope node for `ifStatement blockStatement` node #1 child node #2', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(ifStatementBlockStatementNode1)[1], programNode);
+        });
+
+        it('should return block-scope node for `ifStatement blockStatement` node #2 child node #1', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(ifStatementBlockStatementNode2)[0], functionDeclarationBlockStatementNode);
+        });
+
+        it('should return block-scope node for `ifStatement blockStatement` node #1 child node #2', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(ifStatementBlockStatementNode2)[1], programNode);
+        });
+
+        it('should return block-scope node for `expressionStatement` node #3 child node #1', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(expressionStatementNode3)[0], functionDeclarationBlockStatementNode);
+        });
+
+        it('should return block-scope node for `expressionStatement` node #3 child node #2', () => {
             assert.deepEqual(NodeUtils.getBlockScopesOfNode(expressionStatementNode3)[1], programNode);
         });
 
@@ -265,7 +294,7 @@ describe('NodeUtils', () => {
             functionDeclarationNode2: ESTree.FunctionDeclaration,
             programNode: ESTree.Program;
 
-        beforeEach(() => {
+        before(() => {
             expressionStatementNode1 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
             expressionStatementNode2 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
             expressionStatementNode3 = Nodes.getExpressionStatementNode(Nodes.getIdentifierNode('identifier'));
@@ -319,17 +348,47 @@ describe('NodeUtils', () => {
             ifStatementBlockStatementNode2.parentNode = ifStatementNode2;
         });
 
-        it('should return block-scope depth for given node', () => {
+        it('should return block-scope depth for `program` node', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(programNode), 0);
+        });
+
+        it('should return block-scope depth for `functionDeclaration` node #1', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(functionDeclarationNode1), 0);
+        });
+
+        it('should return block-scope depth for `functionDeclaration blockStatement` node #1', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(functionDeclarationBlockStatementNode1), 1);
+        });
+
+        it('should return block-scope depth for `expressionStatement` node #1', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(expressionStatementNode1), 1);
+        });
+
+        it('should return block-scope depth for `ifStatement` node #1', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(ifStatementNode1), 1);
+        });
+
+        it('should return block-scope depth for `ifStatement blockStatement` node #1', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(ifStatementBlockStatementNode1), 1);
+        });
+
+        it('should return block-scope depth for `functionDeclaration` node #2', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(functionDeclarationNode2), 1);
+        });
+
+        it('should return block-scope depth for `functionDeclaration blockStatement` node #2', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(functionDeclarationBlockStatementNode2), 2);
+        });
+
+        it('should return block-scope depth for `expressionStatement` node #2', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(expressionStatementNode2), 2);
+        });
+
+        it('should return block-scope depth for `ifStatement` node #2', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(ifStatementNode2), 2);
+        });
+
+        it('should return block-scope depth for `ifStatement blockStatement` node #2', () => {
             assert.deepEqual(NodeUtils.getNodeBlockScopeDepth(ifStatementBlockStatementNode2), 2);
         });
 
@@ -339,18 +398,15 @@ describe('NodeUtils', () => {
     });
 
     describe('getUnaryExpressionArgumentNode (unaryExpressionNode: ESTree.UnaryExpression): ESTree.Node', () => {
-        let expressionStatementNode: ESTree.ExpressionStatement,
-            literalNode: ESTree.Literal,
-            unaryExpressionNode1: ESTree.UnaryExpression,
-            unaryExpressionNode2: ESTree.UnaryExpression,
-            programNode: ESTree.Program;
-
-        beforeEach(() => {
-            literalNode = Nodes.getLiteralNode('test');
-            unaryExpressionNode2 = Nodes.getUnaryExpressionNode('!', literalNode)
-            unaryExpressionNode1 = Nodes.getUnaryExpressionNode('!', unaryExpressionNode2)
-            expressionStatementNode = Nodes.getExpressionStatementNode(unaryExpressionNode1);
-            programNode = Nodes.getProgramNode([
+        let expectedNode: ESTree.Literal,
+            unaryExpressionArgumentNode: ESTree.Node;
+
+        before(() => {
+            const literalNode: ESTree.Literal = Nodes.getLiteralNode('test');
+            const unaryExpressionNode2: ESTree.UnaryExpression = Nodes.getUnaryExpressionNode('!', literalNode);
+            const unaryExpressionNode1: ESTree.UnaryExpression = Nodes.getUnaryExpressionNode('!', unaryExpressionNode2);
+            const expressionStatementNode: ESTree.ExpressionStatement = Nodes.getExpressionStatementNode(unaryExpressionNode1);
+            const programNode: ESTree.Program = Nodes.getProgramNode([
                 expressionStatementNode
             ]);
 
@@ -359,10 +415,13 @@ describe('NodeUtils', () => {
             unaryExpressionNode1.parentNode = expressionStatementNode;
             unaryExpressionNode2.parentNode = unaryExpressionNode1;
             literalNode.parentNode = unaryExpressionNode2;
+
+            unaryExpressionArgumentNode = NodeUtils.getUnaryExpressionArgumentNode(unaryExpressionNode1);
+            expectedNode = literalNode;
         });
 
         it('should return unary expression argument node', () => {
-            assert.deepEqual(NodeUtils.getUnaryExpressionArgumentNode(unaryExpressionNode1), literalNode);
+            assert.deepEqual(unaryExpressionArgumentNode, expectedNode);
         });
     });
 
@@ -388,32 +447,61 @@ describe('NodeUtils', () => {
             );
         });
 
-        it('should parentize given AST-tree with `ProgramNode` as root node', () => {
-            programNode = Nodes.getProgramNode([
-                ifStatementNode
-            ]);
+        describe('parentize AST-tree with `ProgramNode` as root node', () => {
+            beforeEach(() => {
+                programNode = Nodes.getProgramNode([
+                    ifStatementNode
+                ]);
+
+                programNode = NodeUtils.parentize(programNode);
+            });
+
+            it('should parentize `program` node with `ProgramNode` as root node', () => {
+                assert.deepEqual(programNode.parentNode, programNode);
+            });
+
+            it('should parentize `ifStatement` node with `ProgramNode` as root node', () => {
+                assert.deepEqual(ifStatementNode.parentNode, programNode);
+            });
+
+            it('should parentize `ifStatement blockStatement` node with `ProgramNode` as root node', () => {
+                assert.deepEqual(ifStatementBlockStatementNode.parentNode, ifStatementNode);
+            });
 
-            programNode = NodeUtils.parentize(programNode);
+            it('should parentize `expressionStatement` node #1 with `ProgramNode` as root node', () => {
+                assert.deepEqual(expressionStatementNode1.parentNode, ifStatementBlockStatementNode);
+            });
 
-            assert.deepEqual(programNode.parentNode, programNode);
-            assert.deepEqual(ifStatementNode.parentNode, programNode);
-            assert.deepEqual(ifStatementBlockStatementNode.parentNode, ifStatementNode);
-            assert.deepEqual(expressionStatementNode1.parentNode, ifStatementBlockStatementNode);
-            assert.deepEqual(expressionStatementNode2.parentNode, ifStatementBlockStatementNode);
+            it('should parentize `expressionStatement` node #2 with `ProgramNode` as root node', () => {
+                assert.deepEqual(expressionStatementNode2.parentNode, ifStatementBlockStatementNode);
+            });
         });
 
-        it('should parentize given AST-tree', () => {
-            programNode = Nodes.getProgramNode([
-                ifStatementNode
-            ]);
-            programNode.parentNode = programNode;
+        describe('parentize AST-tree', () => {
+            beforeEach(() => {
+                programNode = Nodes.getProgramNode([
+                    ifStatementNode
+                ]);
+                programNode.parentNode = programNode;
+
+                ifStatementNode = NodeUtils.parentize(ifStatementNode);
+            });
+
+            it('should parentize `ifStatement` node', () => {
+                assert.deepEqual(ifStatementNode.parentNode, programNode);
+            });
+
+            it('should parentize `ifStatement blockStatement` node', () => {
+                assert.deepEqual(ifStatementBlockStatementNode.parentNode, ifStatementNode);
+            });
 
-            ifStatementNode = NodeUtils.parentize(ifStatementNode);
+            it('should parentize `expressionStatement` node #1', () => {
+                assert.deepEqual(expressionStatementNode1.parentNode, ifStatementBlockStatementNode);
+            });
 
-            assert.deepEqual(ifStatementNode.parentNode, programNode);
-            assert.deepEqual(ifStatementBlockStatementNode.parentNode, ifStatementNode);
-            assert.deepEqual(expressionStatementNode1.parentNode, ifStatementBlockStatementNode);
-            assert.deepEqual(expressionStatementNode2.parentNode, ifStatementBlockStatementNode);
+            it('should parentize `expressionStatement` node #2', () => {
+                assert.deepEqual(expressionStatementNode2.parentNode, ifStatementBlockStatementNode);
+            });
         });
     });
 });

+ 2 - 2
test/unit-tests/obfuscation-result/ObfuscationResult.spec.ts

@@ -10,12 +10,12 @@ describe('ObfuscationResult', () => {
             obfuscationResult: IObfuscationResult,
             sourceMap: string = 'sourceMap';
 
-        beforeEach(() => {
+        before(() => {
             obfuscationResult = new ObfuscationResult();
             obfuscationResult.initialize(obfuscatedCode, sourceMap);
         });
 
-        it('should returns obfuscated code if `.toString()` was called on `ObfuscationResult` object', () => {
+        it('should return obfuscated code if `.toString()` was called on `ObfuscationResult` object', () => {
             assert.equal(obfuscationResult.toString(), obfuscatedCode);
         });
     });

+ 260 - 178
test/unit-tests/options/options-normalizer/OptionsNormalizer.spec.ts

@@ -24,204 +24,286 @@ describe('OptionsNormalizer', () => {
         let optionsPreset: TInputOptions,
             expectedOptionsPreset: TInputOptions;
 
-        it('should normalize options preset: controlFlowFlatteningThresholdRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                controlFlowFlattening: true,
-                controlFlowFlatteningThreshold: 0
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                controlFlowFlattening: false,
-                controlFlowFlatteningThreshold: 0
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('controlFlowFlatteningThresholdRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    controlFlowFlattening: true,
+                    controlFlowFlatteningThreshold: 0
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    controlFlowFlattening: false,
+                    controlFlowFlatteningThreshold: 0
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
 
-        it('should normalize options preset: deadCodeInjectionRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                deadCodeInjection: true,
-                deadCodeInjectionThreshold: 0.4,
-                stringArray: false,
-                stringArrayThreshold: 0
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                deadCodeInjection: true,
-                deadCodeInjectionThreshold: 0.4,
-                stringArray: true,
-                stringArrayThreshold: 0.75
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('deadCodeInjectionRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    deadCodeInjection: true,
+                    deadCodeInjectionThreshold: 0.4,
+                    stringArray: false,
+                    stringArrayThreshold: 0
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    deadCodeInjection: true,
+                    deadCodeInjectionThreshold: 0.4,
+                    stringArray: true,
+                    stringArrayThreshold: 0.75
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
 
-        it('should normalize options preset: deadCodeInjectionRule. `stringArrayThreshold` option is not empty', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                deadCodeInjection: true,
-                deadCodeInjectionThreshold: 0.4,
-                stringArray: false,
-                stringArrayThreshold: 0.5
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                deadCodeInjection: true,
-                deadCodeInjectionThreshold: 0.4,
-                stringArray: true,
-                stringArrayThreshold: 0.5
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('deadCodeInjectionRule', () => {
+            describe('`stringArrayThreshold` option is empty', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...DEFAULT_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 0.4,
+                        stringArray: false,
+                        stringArrayThreshold: 0
+                    });
+
+                    expectedOptionsPreset = {
+                        ...DEFAULT_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 0.4,
+                        stringArray: true,
+                        stringArrayThreshold: 0.75
+                    };
+                });
+
+                it('should normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+
+            describe('`stringArrayThreshold` option is not empty', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...DEFAULT_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 0.4,
+                        stringArray: false,
+                        stringArrayThreshold: 0.5
+                    });
+
+                    expectedOptionsPreset = {
+                        ...DEFAULT_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 0.4,
+                        stringArray: true,
+                        stringArrayThreshold: 0.5
+                    };
+                });
+
+                it('should normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
         });
 
-        it('should normalize options preset: deadCodeInjectionThresholdRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                deadCodeInjection: true,
-                deadCodeInjectionThreshold: 0
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                deadCodeInjection: false,
-                deadCodeInjectionThreshold: 0
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
-        });
-
-        it('should normalize options preset: domainLockRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                domainLock: ['//localhost:9000', 'https://google.ru/abc?cde=fgh']
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                domainLock: ['localhost', 'google.ru']
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('deadCodeInjectionThresholdRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    deadCodeInjection: true,
+                    deadCodeInjectionThreshold: 0
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    deadCodeInjection: false,
+                    deadCodeInjectionThreshold: 0
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
 
-        it('should normalize options preset: selfDefendingRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                selfDefending: true,
-                compact: false
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                selfDefending: true,
-                compact: true
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('domainLockRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    domainLock: [
+                        '//localhost:9000',
+                        'https://google.ru/abc?cde=fgh'
+                    ]
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    domainLock: [
+                        'localhost',
+                        'google.ru'
+                    ]
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
 
-        it('should normalize options preset: sourceMapBaseUrlRule #1', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                sourceMapBaseUrl: 'http://localhost:9000',
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                sourceMapBaseUrl: ''
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('selfDefendingRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    selfDefending: true,
+                    compact: false
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    selfDefending: true,
+                    compact: true
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
 
-        it('should normalize options preset: sourceMapBaseUrlRule #2', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                sourceMapBaseUrl: 'http://localhost:9000',
-                sourceMapFileName: '/outputSourceMapName.map'
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                sourceMapBaseUrl: 'http://localhost:9000/',
-                sourceMapFileName: 'outputSourceMapName.js.map'
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('sourceMapBaseUrlRule', () => {
+            describe('variant #1: only source map base url', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...DEFAULT_PRESET,
+                        sourceMapBaseUrl: 'http://localhost:9000',
+                    });
+
+                    expectedOptionsPreset = {
+                        ...DEFAULT_PRESET,
+                        sourceMapBaseUrl: ''
+                    };
+                });
+
+                it('should normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+
+            describe('variant #2: source map base url with source map file name', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...DEFAULT_PRESET,
+                        sourceMapBaseUrl: 'http://localhost:9000',
+                        sourceMapFileName: '/outputSourceMapName.map'
+                    });
+
+                    expectedOptionsPreset = {
+                        ...DEFAULT_PRESET,
+                        sourceMapBaseUrl: 'http://localhost:9000/',
+                        sourceMapFileName: 'outputSourceMapName.js.map'
+                    };
+                });
+
+                it('should normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
         });
 
-        it('should normalize options preset: sourceMapFileNameRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                sourceMapBaseUrl: 'http://localhost:9000',
-                sourceMapFileName: '//outputSourceMapName'
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                sourceMapBaseUrl: 'http://localhost:9000/',
-                sourceMapFileName: 'outputSourceMapName.js.map'
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('sourceMapFileNameRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    sourceMapBaseUrl: 'http://localhost:9000',
+                    sourceMapFileName: '//outputSourceMapName'
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    sourceMapBaseUrl: 'http://localhost:9000/',
+                    sourceMapFileName: 'outputSourceMapName.js.map'
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
 
-        it('should normalize options preset: stringArrayRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                stringArray: false,
-                stringArrayEncoding: 'rc4',
-                stringArrayThreshold: 0.5,
-                rotateStringArray: true
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                stringArray: false,
-                stringArrayEncoding: false,
-                stringArrayThreshold: 0,
-                rotateStringArray: false
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('stringArrayRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    stringArray: false,
+                    stringArrayEncoding: 'rc4',
+                    stringArrayThreshold: 0.5,
+                    rotateStringArray: true
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    stringArray: false,
+                    stringArrayEncoding: false,
+                    stringArrayThreshold: 0,
+                    rotateStringArray: false
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
 
-        it('should normalize options preset: stringArrayEncodingRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                stringArrayEncoding: true
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                stringArrayEncoding: 'base64'
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('stringArrayEncodingRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    stringArrayEncoding: true
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    stringArrayEncoding: 'base64'
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
 
-        it('should normalize options preset: stringArrayThresholdRule', () => {
-            optionsPreset = {
-                ...DEFAULT_PRESET,
-                rotateStringArray: true,
-                stringArray: true,
-                stringArrayThreshold: 0
-            };
-
-            expectedOptionsPreset = {
-                ...DEFAULT_PRESET,
-                rotateStringArray: false,
-                stringArray: false,
-                stringArrayThreshold: 0
-            };
-
-            assert.deepEqual(getNormalizedOptions(optionsPreset), expectedOptionsPreset);
+        describe('stringArrayThresholdRule', () => {
+            before(() => {
+                optionsPreset = getNormalizedOptions({
+                    ...DEFAULT_PRESET,
+                    rotateStringArray: true,
+                    stringArray: true,
+                    stringArrayThreshold: 0
+                });
+
+                expectedOptionsPreset = {
+                    ...DEFAULT_PRESET,
+                    rotateStringArray: false,
+                    stringArray: false,
+                    stringArrayThreshold: 0
+                };
+            });
+
+            it('should normalize options preset', () => {
+                assert.deepEqual(optionsPreset, expectedOptionsPreset);
+            });
         });
     });
 });

+ 59 - 37
test/unit-tests/source-map-corrector/SourceMapCorrector.spec.ts

@@ -40,60 +40,82 @@ function getCorrectedObfuscationResult (
 
 describe('SourceMapCorrector', () => {
     describe('correct (): IObfuscationResult', () => {
-        let expectedObfuscationResult: IObfuscationResult,
-            obfuscatedCode: string = 'var test = 1;',
-            sourceMap: string = 'test';
-
-        it('should return untouched obfuscated code if source map does not exist', () => {
-            expectedObfuscationResult = getCorrectedObfuscationResult(
-                obfuscatedCode,
-                '',
-                '',
-                '',
-                SourceMapMode.Separate)
-            ;
-
-            assert.equal(expectedObfuscationResult.getObfuscatedCode(), obfuscatedCode);
+        const expectedObfuscatedCode: string = 'var test = 1;';
+        const sourceMap: string = 'test';
+
+        let obfuscationResult: IObfuscationResult,
+            obfuscatedCode: string;
+
+        describe('source map doest\'t exist', () => {
+            before(() => {
+                obfuscationResult = getCorrectedObfuscationResult(
+                    expectedObfuscatedCode,
+                    '',
+                    '',
+                    '',
+                    SourceMapMode.Separate
+                );
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should return untouched obfuscated code', () => {
+                assert.equal(obfuscatedCode, expectedObfuscatedCode);
+            });
         });
 
-        describe('if source map is set and source map mode is `inline`', () => {
-            before (() => {
-                expectedObfuscationResult = getCorrectedObfuscationResult(
-                    obfuscatedCode,
+        describe('source map is set, source map mode is `inline`', () => {
+            const regExp: RegExp = /data:application\/json;base64/;
+
+            before(() => {
+                obfuscationResult = getCorrectedObfuscationResult(
+                    expectedObfuscatedCode,
                     sourceMap,
                     '',
                     '',
                     SourceMapMode.Inline
                 );
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('should add source map to obfuscated code as base64 encoded string', () => {
-                assert.match(expectedObfuscationResult.getObfuscatedCode(), /data:application\/json;base64/);
+                assert.match(obfuscatedCode, regExp);
             });
         });
 
-        it('should add source map import to obfuscated code if source map mode is `separate`', () => {
-            expectedObfuscationResult = getCorrectedObfuscationResult(
-                obfuscatedCode,
-                sourceMap,
-                'http://example.com',
-                'output.js.map',
-                SourceMapMode.Separate
-            );
+        describe('source map mode is `separate`', () => {
+            const regExp: RegExp = /sourceMappingURL=http:\/\/example\.com\/output\.js\.map/;
 
-            assert.match(expectedObfuscationResult.getObfuscatedCode(), /sourceMappingURL=http:\/\/example\.com\/output\.js\.map/);
+            before(() => {
+                obfuscationResult = getCorrectedObfuscationResult(
+                    expectedObfuscatedCode,
+                    sourceMap,
+                    'http://example.com',
+                    'output.js.map',
+                    SourceMapMode.Separate
+                );
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should add source map import to obfuscated code', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
         });
 
-        it('should not touch obfuscated code if source map mode is `separate` and `sourceMapUrl` is not set', () => {
-            expectedObfuscationResult = getCorrectedObfuscationResult(
-                obfuscatedCode,
-                sourceMap,
-                '',
-                '',
-                SourceMapMode.Separate
-            );
+        describe('source map mode is `separate`, `sourceMapUrl` is not set', () => {
+            before(() => {
+                obfuscationResult = getCorrectedObfuscationResult(
+                    expectedObfuscatedCode,
+                    sourceMap,
+                    '',
+                    '',
+                    SourceMapMode.Separate
+                );
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
 
-            assert.equal(expectedObfuscationResult.getObfuscatedCode(), obfuscatedCode);
+            it('should not touch obfuscated code', () => {
+                assert.equal(obfuscatedCode, expectedObfuscatedCode);
+            });
         });
     });
 });

+ 52 - 10
test/unit-tests/stack-trace-analyzer/stack-trace-analyzer/StackTraceAnalyzer.spec.ts

@@ -4,16 +4,58 @@ import { StackTraceAnalyzer } from '../../../../src/stack-trace-analyzer/StackTr
 
 describe('StackTraceAnalyzer', () => {
     describe('getLimitIndex (blockScopeBodyLength: number): number', () => {
-        it('should returns correct limit index based on block scope body length', () => {
-            const blockScopeBodyLength1: number = 10000;
-            const blockScopeBodyLength2: number = 1000;
-            const blockScopeBodyLength3: number = 25;
-            const blockScopeBodyLength4: number = 5;
-
-            assert.equal(StackTraceAnalyzer.getLimitIndex(blockScopeBodyLength1), 44);
-            assert.equal(StackTraceAnalyzer.getLimitIndex(blockScopeBodyLength2), 26);
-            assert.equal(StackTraceAnalyzer.getLimitIndex(blockScopeBodyLength3), 24);
-            assert.equal(StackTraceAnalyzer.getLimitIndex(blockScopeBodyLength4), 4);
+        let limitIndex: number;
+
+        describe('variant #1: length - 10000', () => {
+            const blockScopeBodyLength: number = 10000;
+            const expectedLimitIndex: number = 44;
+
+            before(() => {
+                limitIndex = StackTraceAnalyzer.getLimitIndex(blockScopeBodyLength);
+            });
+
+            it('should return correct limit index based on block scope body length', () => {
+                assert.equal(limitIndex, expectedLimitIndex);
+            });
+        });
+
+        describe('variant #2: length - 1000', () => {
+            const blockScopeBodyLength: number = 1000;
+            const expectedLimitIndex: number = 26;
+
+            before(() => {
+                limitIndex = StackTraceAnalyzer.getLimitIndex(blockScopeBodyLength);
+            });
+
+            it('should return correct limit index based on block scope body length', () => {
+                assert.equal(limitIndex, expectedLimitIndex);
+            });
+        });
+
+        describe('variant #3: length - 25', () => {
+            const blockScopeBodyLength: number = 25;
+            const expectedLimitIndex: number = 24;
+
+            before(() => {
+                limitIndex = StackTraceAnalyzer.getLimitIndex(blockScopeBodyLength);
+            });
+
+            it('should return correct limit index based on block scope body length', () => {
+                assert.equal(limitIndex, expectedLimitIndex);
+            });
+        });
+
+        describe('variant #4: length - 5', () => {
+            const blockScopeBodyLength: number = 5;
+            const expectedLimitIndex: number = 4;
+
+            before(() => {
+                limitIndex = StackTraceAnalyzer.getLimitIndex(blockScopeBodyLength);
+            });
+
+            it('should return correct limit index based on block scope body length', () => {
+                assert.equal(limitIndex, expectedLimitIndex);
+            });
         });
     });
 });

+ 132 - 54
test/unit-tests/storages/ArrayStorage.spec.ts

@@ -10,117 +10,195 @@ class ConcreteStorage extends ArrayStorage <string> {
     }
 }
 
+/**
+ * @type {IStorage<any>}
+ */
+const getStorageInstance = (): IStorage <any> => {
+    const storage: IStorage<any> = new ConcreteStorage();
+
+    storage.initialize();
+
+    return storage;
+};
+
 describe('ArrayStorage', () => {
+    const storageKey: number = 0;
+    const storageValue: string = 'foo';
+
+    let storage: IStorage <any>;
+
     describe('initialize (...args: any[]): void', () => {
-        it('should throws an error when storage isn\'t initialized', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        const expectedError: ErrorConstructor = Error;
 
-            assert.throws(() => storage.set(0, 'foo'), Error);
+        let testFunc: () => void;
+
+        before(() => {
+            storage = new ConcreteStorage();
+            testFunc = () => storage.set(storageKey, storageValue);
+        });
+
+        it('should throws an error when storage isn\'t initialized', () => {
+            assert.throws(testFunc, expectedError);
         });
     });
 
     describe('getStorage (): T[]', () => {
-        it('should returns storage', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        const expectedInstanceOf: ArrayConstructor = Array;
+
+        let arrayStorage: string[];
+
+        before(() => {
+            storage = getStorageInstance();
 
-            storage.initialize();
+            arrayStorage = storage.getStorage();
+        });
 
-            assert.instanceOf(storage.getStorage(), Array);
+        it('should return storage', () => {
+            assert.instanceOf(arrayStorage, expectedInstanceOf);
         });
     });
 
     describe('get (key: number): T', () => {
-        it('should returns value from storage by key', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        describe('variant #1: value exist', () => {
+            const expectedValue: string = storageValue;
+
+            let value: string;
+
+            before(() => {
+                storage = getStorageInstance();
+                storage.set(storageKey, storageValue);
 
-            storage.initialize();
-            storage.set(0, 'foo');
+                value = storage.get(storageKey);
+            });
 
-            assert.equal(storage.get(0), 'foo');
+            it('should return value from storage by key', () => {
+                assert.equal(value, expectedValue);
+            });
         });
 
-        it('should throw an error if value isn\'t exist', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        describe('variant #2: value isn\'t exist', () => {
+            const expectedError: ErrorConstructor = Error;
+
+            let testFunc: () => void;
 
-            storage.initialize();
+            before(() => {
+                storage = getStorageInstance();
 
-            assert.throws(() => storage.get(0), Error);
+                testFunc = () => storage.get(storageKey);
+            });
+
+            it('should throw an error', () => {
+                assert.throws(testFunc, expectedError);
+            });
         });
     });
 
     describe('getLength (): number', () => {
-        it('should returns length of storage', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        const expectedStorageLength: number = 1;
 
-            storage.initialize();
-            storage.set(0, 'foo');
+        let storageLength: number;
+
+        before(() => {
+            storage = getStorageInstance();
+            storage.set(storageKey, storageValue);
+
+            storageLength = storage.getLength();
+        });
 
-            assert.equal(storage.getLength(), 1);
+        it('should return length of storage', () => {
+            assert.equal(storageLength, expectedStorageLength);
         });
     });
 
     describe('getKeyOf (value: T): number | null', () => {
-        it('should returns key of string value', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        let key: string | number | null;
 
-            storage.initialize();
-            storage.set(0, 'foo');
+        describe('variant #1', () => {
+            before(() => {
+                storage = getStorageInstance();
+                storage.set(storageKey, storageValue);
 
-            assert.equal(storage.getKeyOf('foo'), 0);
+                key = storage.getKeyOf(storageValue);
+            });
+
+            it('should return key of string value', () => {
+                assert.equal(key, storageKey);
+            });
         });
 
-        it('should returns key of object if objects in `set` and `get` are two linked objects', () => {
-            const storage: IStorage <Object> = new ConcreteStorage();
+        describe('variant #2', () => {
             const object: Object = {
                 foo: 'bar'
             };
 
-            storage.initialize();
-            storage.set(0, object);
+            before(() => {
+                storage = getStorageInstance();
+                storage.set(storageKey, object);
 
-            assert.equal(storage.getKeyOf(object), 0);
-        });
+                key = storage.getKeyOf(object);
+            });
 
-        it('should return `null` if objects in `set` and `get` are two equal objects', () => {
-            const storage: IStorage <Object> = new ConcreteStorage();
+            it('should return key of object if objects in `set` and `get` are two same objects', () => {
+                assert.equal(key, storageKey);
+            });
+        });
 
-            storage.initialize();
-            storage.set(0, {
+        describe('variant #3', () => {
+            const expectedKey: null = null;
+            const object: Object = {
                 foo: 'bar'
+            };
+
+            before(() => {
+                storage = getStorageInstance();
+                storage.set(storageKey, object);
+
+                key = storage.getKeyOf({...object});
             });
 
-            assert.equal(storage.getKeyOf({
-                foo: 'bar'
-            }), null);
+            it('should return `null` if objects in `set` and `get` are two different objects', () => {
+                assert.equal(key, expectedKey);
+            });
         });
     });
 
     describe('set (key: number, value: T): void', () => {
-        it('should set value to the storage', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        let value: string;
+
+        before(() => {
+            storage = getStorageInstance();
+            storage.set(storageKey, storageValue);
 
-            storage.initialize();
-            storage.set(0, 'foo');
+            value = storage.get(storageKey);
+        });
 
-            assert.equal(storage.get(0), 'foo');
-            assert.equal(storage.getLength(), 1);
+        it('should set value to the storage', () => {
+            assert.equal(value, storageValue);
         });
     });
 
     describe('mergeWith (storage: this, mergeId: boolean = false): void', () => {
-        it('should merge two storages', () => {
-            const storage1: IStorage <string> = new ConcreteStorage();
-            const storage2: IStorage <string> = new ConcreteStorage();
+        const secondStorageKey: number = 1;
+        const secondStorageValue: string = 'bar';
 
-            storage1.initialize();
-            storage1.set(0, 'foo');
+        const expectedArray: string[] = [storageValue, secondStorageValue];
 
-            storage2.initialize();
-            storage2.set(1, 'bar');
+        let array: string[];
 
-            storage1.mergeWith(storage2, false);
+        before(() => {
+            storage = getStorageInstance();
+            storage.set(storageKey, storageValue);
 
-            assert.deepEqual(storage1.getStorage(), ['foo', 'bar']);
+            const secondStorage: IStorage <string> = getStorageInstance();
+            secondStorage.set(secondStorageKey, secondStorageValue);
+
+            storage.mergeWith(secondStorage, false);
+
+            array = storage.getStorage();
+        });
+
+        it('should merge two storages', () => {
+            assert.deepEqual(array, expectedArray);
         });
     });
 });

+ 135 - 54
test/unit-tests/storages/MapStorage.spec.ts

@@ -10,117 +10,198 @@ class ConcreteStorage extends MapStorage <string> {
     }
 }
 
+/**
+ * @type {IStorage<any>}
+ */
+const getStorageInstance = (): IStorage <any> => {
+    const storage: IStorage<any> = new ConcreteStorage();
+
+    storage.initialize();
+
+    return storage;
+};
+
 describe('MapStorage', () => {
+    const storageKey: string = 'foo';
+    const storageValue: string = 'bar';
+
+    let storage: IStorage <any>;
+
     describe('initialize (...args: any[]): void', () => {
-        it('should throws an error when storage isn\'t initialized', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        const expectedError: ErrorConstructor = Error;
 
-            assert.throws(() => storage.set('foo', 'bar'), Error);
+        let testFunc: () => void;
+
+        before(() => {
+            storage = new ConcreteStorage();
+            testFunc = () => storage.set(storageKey, storageValue);
+        });
+
+        it('should throws an error when storage isn\'t initialized', () => {
+            assert.throws(testFunc, expectedError);
         });
     });
 
     describe('getStorage (): Map <string | number, T>', () => {
-        it('should returns storage', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        const expectedInstanceOf: MapConstructor = Map;
+
+        let mapStorage: string[];
+
+        before(() => {
+            storage = getStorageInstance();
 
-            storage.initialize();
+            mapStorage = storage.getStorage();
+        });
 
-            assert.instanceOf(storage.getStorage(), Map);
+        it('should return storage', () => {
+            assert.instanceOf(mapStorage, expectedInstanceOf);
         });
     });
 
     describe('get (key: string | number): T', () => {
-        it('should returns value from storage by key', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        describe('variant #1: value exist', () => {
+            const expectedValue: string = storageValue;
+
+            let value: string;
+
+            before(() => {
+                storage = getStorageInstance();
+                storage.set(storageKey, storageValue);
 
-            storage.initialize();
-            storage.set('foo', 'bar');
+                value = storage.get(storageKey);
+            });
 
-            assert.equal(storage.get('foo'), 'bar');
+            it('should return value from storage by key', () => {
+                assert.equal(value, expectedValue);
+            });
         });
 
-        it('should throw an error if value isn\'t exist', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        describe('variant #2: value isn\'t exist', () => {
+            const expectedError: ErrorConstructor = Error;
+
+            let testFunc: () => void;
 
-            storage.initialize();
+            before(() => {
+                storage = getStorageInstance();
 
-            assert.throws(() => storage.get('foo'), Error);
+                testFunc = () => storage.get(storageKey);
+            });
+
+            it('should throw an error', () => {
+                assert.throws(testFunc, expectedError);
+            });
         });
     });
 
     describe('getLength (): number', () => {
-        it('should returns length of storage', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        const expectedStorageLength: number = 1;
 
-            storage.initialize();
-            storage.set('foo', 'bar');
+        let storageLength: number;
+
+        before(() => {
+            storage = getStorageInstance();
+            storage.set(storageKey, storageValue);
+
+            storageLength = storage.getLength();
+        });
 
-            assert.equal(storage.getLength(), 1);
+        it('should return length of storage', () => {
+            assert.equal(storageLength, expectedStorageLength);
         });
     });
 
     describe('getKeyOf (value: T): string | number | null', () => {
-        it('should returns key of string value', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        let key: string | number | null;
 
-            storage.initialize();
-            storage.set('foo', 'bar');
+        describe('variant #1', () => {
+            before(() => {
+                storage = getStorageInstance();
+                storage.set(storageKey, storageValue);
 
-            assert.equal(storage.getKeyOf('bar'), 'foo');
+                key = storage.getKeyOf(storageValue);
+            });
+
+            it('should return key of string value', () => {
+                assert.equal(key, storageKey);
+            });
         });
 
-        it('should returns key of object if objects in `set` and `get` are two linked objects', () => {
-            const storage: IStorage <Object> = new ConcreteStorage();
+        describe('variant #2', () => {
             const object: Object = {
                 bar: 'baz'
             };
 
-            storage.initialize();
-            storage.set('foo', object);
+            before(() => {
+                storage = getStorageInstance();
+                storage.set(storageKey, object);
 
-            assert.equal(storage.getKeyOf(object), 'foo');
-        });
+                key = storage.getKeyOf(object);
+            });
 
-        it('should return `null` if objects in `set` and `get` are two equal objects', () => {
-            const storage: IStorage <Object> = new ConcreteStorage();
+            it('should return key of object if objects in `set` and `get` are two same objects', () => {
+                assert.equal(key, storageKey);
+            });
+        });
 
-            storage.initialize();
-            storage.set('foo', {
+        describe('variant #3', () => {
+            const expectedKey: null = null;
+            const object: Object = {
                 bar: 'baz'
+            };
+
+            before(() => {
+                storage = getStorageInstance();
+                storage.set(storageKey, object);
+
+                key = storage.getKeyOf({...object});
             });
 
-            assert.equal(storage.getKeyOf({
-                bar: 'baz'
-            }), null);
+            it('should return `null` if objects in `set` and `get` are two different objects', () => {
+                assert.equal(key, expectedKey);
+            });
         });
     });
 
     describe('set (key: string | number, value: T): void', () => {
-        it('should set value to the storage', () => {
-            const storage: IStorage <string> = new ConcreteStorage();
+        let value: string;
+
+        before(() => {
+            storage = getStorageInstance();
+            storage.set(storageKey, storageValue);
 
-            storage.initialize();
-            storage.set('foo', 'bar');
+            value = storage.get(storageKey);
+        });
 
-            assert.equal(storage.get('foo'), 'bar');
-            assert.equal(storage.getLength(), 1);
+        it('should set value to the storage', () => {
+            assert.equal(value, storageValue);
         });
     });
 
     describe('mergeWith (storage: this, mergeId: boolean = false): void', () => {
-        it('should merge two storages', () => {
-            const storage1: IStorage <string> = new ConcreteStorage();
-            const storage2: IStorage <string> = new ConcreteStorage();
+        const secondStorageKey: string = 'baz';
+        const secondStorageValue: string = 'quux';
 
-            storage1.initialize();
-            storage1.set('foo', 'bar');
+        const expectedArray: string[][] = [
+            [storageKey, storageValue],
+            [secondStorageKey, secondStorageValue]
+        ];
 
-            storage2.initialize();
-            storage2.set('baz', 'quux');
+        let array: string[][];
 
-            storage1.mergeWith(storage2, false);
+        before(() => {
+            storage = getStorageInstance();
+            storage.set(storageKey, storageValue);
 
-            assert.deepEqual(Array.from(storage1.getStorage()), [['foo', 'bar'], ['baz', 'quux']]);
+            const secondStorage: IStorage <string> = getStorageInstance();
+            secondStorage.set(secondStorageKey, secondStorageValue);
+
+            storage.mergeWith(secondStorage, false);
+
+            array = <any>Array.from(storage.getStorage());
+        });
+
+        it('should merge two storages', () => {
+            assert.deepEqual(array, expectedArray);
         });
     });
 });

+ 56 - 14
test/unit-tests/utils/crypt-utils/CryptUtils.spec.ts

@@ -4,35 +4,77 @@ import { CryptUtils } from '../../../../src/utils/CryptUtils';
 
 describe('CryptUtils', () => {
     describe('btoa (string: string): string', () => {
+        const expectedString: string = 'c3RyaW5n';
+
+        let string: string;
+
+        before(() => {
+            string = CryptUtils.btoa('string');
+        });
+
         it('should create a base-64 encoded string from a given string', () => {
-            assert.equal(CryptUtils.btoa('string'), 'c3RyaW5n');
+            assert.equal(string, expectedString);
         });
     });
 
     describe('hideString (str: string, length: number): [string, string]', () => {
-        let original1: string = 'example.com',
-            [str1, diff] = CryptUtils.hideString(original1, 30);
+        const originalString: string = 'example.com';
+        const hiddenStringLength: number = 30;
+
+        let hiddenString: string,
+            diffString: string;
 
-        it('should return a string with the original string within', () => {
-            assert.isTrue(str1.length > original1.length);
-            assert.equal(str1.replace(new RegExp('[' + diff + ']', 'g'), ''), original1);
+        before(() => {
+            [hiddenString, diffString] = CryptUtils.hideString(originalString, hiddenStringLength);
         });
 
+        describe('hidden string length check', () => {
+            let originalStringActualLength: number,
+                hiddenStringActualLength: number;
+
+            before(() => {
+                originalStringActualLength = originalString.length;
+                hiddenStringActualLength = hiddenString.length;
+            });
+
+            it('should create hidden string with length equal or bigger than given length', () => {
+                assert.isTrue(hiddenStringActualLength > originalStringActualLength);
+            });
+        });
+
+        describe('hidden string content', () => {
+            let hiddenStringWithoutDiff: string;
+
+            before(() => {
+                const regExp: RegExp = new RegExp(`[${diffString}]`, 'g');
+
+                hiddenStringWithoutDiff = hiddenString.replace(regExp, '');
+            });
+
+            it('should return a hidden string with the original string within', () => {
+                assert.equal(hiddenStringWithoutDiff, originalString);
+            });
+        });
     });
 
     describe('rc4 (string: string, key: string): string', () => {
-        it('should encode string using the rc4 algorithm', () => {
-            const string: string = 'test';
-            const key: string = 'key';
+        const string: string = 'test';
+        const key: string = 'key';
+
+        let encodedString: string,
+            decodedString: string;
 
-            assert.notEqual(CryptUtils.rc4(string, key), string);
+        before(() => {
+            encodedString = CryptUtils.rc4(string, key);
+            decodedString = CryptUtils.rc4(encodedString, key);
         });
 
-        it('should encode and successfully decode string using the rc4 algorithm', () => {
-            const string: string = 'test';
-            const key: string = 'key';
+        it('should encode string using the rc4 algorithm', () => {
+            assert.notEqual(encodedString, string);
+        });
 
-            assert.equal(CryptUtils.rc4(CryptUtils.rc4(string, key), key), string);
+        it('should encode and successfully decode string using the rc4 algorithm', () => {
+            assert.equal(decodedString, string);
         });
     });
 });

+ 23 - 3
test/unit-tests/utils/random-generator-utils/RandomGeneratorUtils.spec.ts

@@ -4,9 +4,29 @@ import { RandomGeneratorUtils } from '../../../../src/utils/RandomGeneratorUtils
 
 describe('RandomGeneratorUtils', () => {
     describe('getRandomVariableName (length: number = 6): string', () => {
-        it('should return a string of given length with random variable name', () => {
-            assert.match(RandomGeneratorUtils.getRandomVariableName(4), /^_0x(\w){4}$/);
-            assert.match(RandomGeneratorUtils.getRandomVariableName(6), /^_0x(\w){4,6}$/);
+        let randomVariableName: string,
+            regExp: RegExp;
+
+        describe('variant #1: string with random variable of length `4`', () => {
+            before(() => {
+                randomVariableName = RandomGeneratorUtils.getRandomVariableName(4);
+                regExp = /^_0x(\w){4}$/;
+            });
+
+            it('should return random variable name', () => {
+                assert.match(randomVariableName, regExp);
+            })
+        });
+
+        describe('variant #2: string with random variable of length `6`', () => {
+            before(() => {
+                randomVariableName = RandomGeneratorUtils.getRandomVariableName(6);
+                regExp = /^_0x(\w){4,6}$/;
+            });
+
+            it('should return random variable name', () => {
+                assert.match(randomVariableName, regExp);
+            })
         });
     });
 });

+ 306 - 48
test/unit-tests/utils/utils/Utils.spec.ts

@@ -6,108 +6,366 @@ import { JSFuck } from '../../../../src/enums/JSFuck';
 
 describe('Utils', () => {
     describe('arrayRange (length: number): number[]', () => {
-        it('should return array with range of numbers', () => {
-            assert.deepEqual(Utils.arrayRange(5), [0, 1, 2, 3, 4]);
+        describe('range length more than 0', () => {
+            const rangeLength: number = 5;
+            const expectedArray: number[] = [0, 1, 2, 3, 4];
+
+            let array: number[];
+
+            before(() => {
+                array = Utils.arrayRange(rangeLength);
+            });
+
+            it('should return array with range of numbers', () => {
+                assert.deepEqual(array, expectedArray);
+            });
         });
 
+        describe('range length is 0', () => {
+            const rangeLength: number = 0;
+            const expectedArray: number[] = [];
+
+            let array: number[];
+
+            before(() => {
+                array = Utils.arrayRange(rangeLength);
+            });
 
-        it('should return empty array if length is 0', () => {
-            assert.deepEqual(Utils.arrayRange(0), []);
+            it('should return empty array', () => {
+                assert.deepEqual(array, expectedArray);
+            });
         });
 
-        it('should return empty array if length less then 0', () => {
-            assert.deepEqual(Utils.arrayRange(-5), []);
+        describe('range length less than 0', () => {
+            const rangeLength: number = -5;
+            const expectedArray: number[] = [];
+
+            let array: number[];
+
+            before(() => {
+                array = Utils.arrayRange(rangeLength);
+            });
+
+            it('should return empty array', () => {
+                assert.deepEqual(array, expectedArray);
+            });
         });
     });
 
     describe('arrayRotate <T> (array: T[], times: number): T[]', () => {
-        let array: number[];
+        let array: number[],
+            rotatedArray: number[];
 
         beforeEach(() => {
             array = [1, 2, 3, 4, 5, 6];
         });
 
-        it('should rotate (shift) array by a given value', () => {
-            assert.deepEqual(Utils.arrayRotate(array, 2), [5, 6, 1, 2, 3, 4]);
+        describe('value is not 0', () => {
+            const rotateValue: number = 2;
+            const expectedArray: number[] = [5, 6, 1, 2, 3, 4];
+
+            beforeEach(() => {
+                rotatedArray = Utils.arrayRotate(array, rotateValue);
+            });
+
+            it('should rotate (shift) array by a given value', () => {
+                assert.deepEqual(rotatedArray, expectedArray);
+            });
         });
 
+        describe('value equals or less 0', () => {
+            const rotateValue: number = 0;
+            const expectedArray: number[] = [1, 2, 3, 4, 5, 6];
 
-        it('should do nothing if value <= 0', () => {
-            assert.deepEqual(Utils.arrayRotate(array, 0), [1, 2, 3, 4, 5, 6]);
-            assert.deepEqual(Utils.arrayRotate(array, -1), [1, 2, 3, 4, 5, 6]);
+            beforeEach(() => {
+                rotatedArray = Utils.arrayRotate(array, rotateValue);
+            });
+
+            it('shouldn\'t rotate array', () => {
+                assert.deepEqual(rotatedArray, expectedArray);
+            });
         });
 
-        it('should throw exception if array is empty', () => {
-            assert.throws(() => Utils.arrayRotate([], 5), ReferenceError);
+        describe('empty array', () => {
+            const emptyArray: number[] = [];
+            const rotateValue: number = 5;
+            const expectedError: ReferenceErrorConstructor = ReferenceError;
+
+            let testFunc: () => void;
+
+            beforeEach(() => {
+                testFunc = () => Utils.arrayRotate(emptyArray, rotateValue);
+            });
+
+            it('should throw exception if array is empty', () => {
+                assert.throws(testFunc, expectedError);
+            });
         });
     });
 
     describe('decToHex (dec: number): string', () => {
-        it('should creates a string with hexadecimal value from a given decimal number', () => {
-            assert.equal(Utils.decToHex(0), '0');
-            assert.equal(Utils.decToHex(10), 'a');
-            assert.equal(Utils.decToHex(17), '11');
-            assert.equal(Utils.decToHex(536870912), '20000000');
+        describe('variant #1: number `0`', () => {
+            const number: number = 0;
+            const expectedHexString = '0';
+
+            let hexString: string;
+
+            before(() => {
+                hexString = Utils.decToHex(number);
+            });
+
+            it('should create a string with hexadecimal value from a given decimal number', () => {
+                assert.equal(hexString, expectedHexString);
+            });
+        });
+
+        describe('variant #2: number `10`', () => {
+            const number: number = 10;
+            const expectedHexString = 'a';
+
+            let hexString: string;
+
+            before(() => {
+                hexString = Utils.decToHex(number);
+            });
+
+            it('should create a string with hexadecimal value from a given decimal number', () => {
+                assert.equal(hexString, expectedHexString);
+            });
+        });
+
+        describe('variant #3: number `17`', () => {
+            const number: number = 17;
+            const expectedHexString = '11';
+
+            let hexString: string;
+
+            before(() => {
+                hexString = Utils.decToHex(number);
+            });
+
+            it('should create a string with hexadecimal value from a given decimal number', () => {
+                assert.equal(hexString, expectedHexString);
+            });
+        });
+
+        describe('variant #4: number `536870912`', () => {
+            const number: number = 536870912;
+            const expectedHexString = '20000000';
+
+            let hexString: string;
+
+            before(() => {
+                hexString = Utils.decToHex(number);
+            });
+
+            it('should create a string with hexadecimal value from a given decimal number', () => {
+                assert.equal(hexString, expectedHexString);
+            });
         });
     });
 
     describe('extractDomainFromUrl (url: string): string', () => {
-        it('should extract domain from the given URL', () => {
-            assert.equal(Utils.extractDomainFromUrl('http://google.ru'), 'google.ru');
-            assert.equal(Utils.extractDomainFromUrl('http://www.google.ru'), 'www.google.ru');
-            assert.equal(Utils.extractDomainFromUrl('https://www.google.ru:9000'), 'www.google.ru');
-            assert.equal(Utils.extractDomainFromUrl('//google.ru/abc'), 'google.ru');
-            assert.equal(Utils.extractDomainFromUrl('//localhost:9000'), 'localhost');
+        describe('variant #1: simple url', () => {
+            const url: string = 'http://google.ru';
+            const expectedDomain: string = 'google.ru';
+
+            let domain: string;
+
+            before(() => {
+                domain = Utils.extractDomainFromUrl(url);
+            });
+
+            it('should extract domain from the given URL', () => {
+                assert.equal(domain, expectedDomain);
+            });
+        });
+
+        describe('variant #2: url with `www` part', () => {
+            const url: string = 'http://www.google.ru';
+            const expectedDomain: string = 'www.google.ru';
+
+            let domain: string;
+
+            before(() => {
+                domain = Utils.extractDomainFromUrl(url);
+            });
+
+            it('should extract domain from the given URL', () => {
+                assert.equal(domain, expectedDomain);
+            });
+        });
+
+        describe('variant #3: url with `https` protocol and port', () => {
+            const url: string = 'https://www.google.ru:9000';
+            const expectedDomain: string = 'www.google.ru';
+
+            let domain: string;
+
+            before(() => {
+                domain = Utils.extractDomainFromUrl(url);
+            });
+
+            it('should extract domain from the given URL', () => {
+                assert.equal(domain, expectedDomain);
+            });
+        });
+
+        describe('variant #4: protocol-wide url and route', () => {
+            const url: string = '//google.ru/abc';
+            const expectedDomain: string = 'google.ru';
+
+            let domain: string;
+
+            before(() => {
+                domain = Utils.extractDomainFromUrl(url);
+            });
+
+            it('should extract domain from the given URL', () => {
+                assert.equal(domain, expectedDomain);
+            });
+        });
+
+        describe('variant #5: protocol-wide url, `localhost` and port', () => {
+            const url: string = '//localhost:9000';
+            const expectedDomain: string = 'localhost';
+
+            let domain: string;
+
+            before(() => {
+                domain = Utils.extractDomainFromUrl(url);
+            });
+
+            it('should extract domain from the given URL', () => {
+                assert.equal(domain, expectedDomain);
+            });
         });
     });
 
     describe('isCeilNumber (number: number): boolean', () => {
-        it('should return true if given number is a ceil', () => {
-            assert.equal(Utils.isCeilNumber(4), true);
-            assert.equal(Utils.isCeilNumber(4.5), false);
+        describe('given number is a ceil', () => {
+            const number: number = 4;
+            const expectedResult: boolean = true;
+
+            let result: boolean;
+
+            before(() => {
+                result = Utils.isCeilNumber(number);
+            });
+
+            it('should return true', () => {
+                assert.equal(result, expectedResult);
+            });
+        });
+
+        describe('given number is a float', () => {
+            const number: number = 4.5;
+            const expectedResult: boolean = false;
+
+            let result: boolean;
+
+            before(() => {
+                result = Utils.isCeilNumber(number);
+            });
+
+            it('should return false', () => {
+                assert.equal(result, expectedResult);
+            });
         });
     });
 
     describe('stringRotate (string: string, times: number): string', () => {
-        let string: string;
+        const string: string = 'abcdefg';
 
-        beforeEach(() => {
-            string = 'abcdefg';
-        });
+        let rotatedString: string;
 
-        it('should rotate string by a given value', () => {
-            assert.deepEqual(Utils.stringRotate(string, 2), 'fgabcde');
+        describe('value is not 0', () => {
+            const rotateValue: number = 2;
+            const expectedString: string = 'fgabcde';
+
+            before(() => {
+                rotatedString = Utils.stringRotate(string, rotateValue);
+            });
+
+            it('should rotate string by a given value', () => {
+                assert.deepEqual(rotatedString, expectedString);
+            });
         });
 
+        describe('value equals or less 0', () => {
+            const rotateValue: number = 0;
+            const expectedString: string = 'abcdefg';
+
+            before(() => {
+                rotatedString = Utils.stringRotate(string, rotateValue);
+            });
 
-        it('should do nothing if value <= 0', () => {
-            assert.deepEqual(Utils.stringRotate(string, 0), 'abcdefg');
-            assert.deepEqual(Utils.stringRotate(string, -1), 'abcdefg');
+            it('shouldn\'t rotate string', () => {
+                assert.deepEqual(rotatedString, expectedString);
+            });
         });
 
-        it('should throw exception if string is empty', () => {
-            assert.throws(() => Utils.stringRotate('', 5), ReferenceError);
+        describe('empty array', () => {
+            const emptyString: string = '';
+            const rotateValue: number = 5;
+            const expectedError: ReferenceErrorConstructor = ReferenceError;
+
+            let testFunc: () => void ;
+
+            before(() => {
+                testFunc = () => Utils.stringRotate(emptyString, rotateValue);
+            });
+
+            it('should throw exception if string is empty', () => {
+                assert.throws(testFunc, expectedError);
+            });
         });
     });
 
     describe('stringToJSFuck (string: string): string', () => {
-        let expected: string = `${JSFuck.s} + ${JSFuck.t} + ${JSFuck.r} + ${JSFuck.i} + ${JSFuck.n} + ${JSFuck.g}`;
+        const string: string = 'string';
+        const expectedString: string = `${JSFuck.s} + ${JSFuck.t} + ${JSFuck.r} + ${JSFuck.i} + ${JSFuck.n} + ${JSFuck.g}`;
+
+        let actualString: string;
 
-        it('should creates a JSFuck encoded string from a given string', () => {
-            assert.equal(Utils.stringToJSFuck('string'), expected);
+        before(() => {
+            actualString = Utils.stringToJSFuck(string);
+        });
+
+        it('should create a JSFuck encoded string from a given string', () => {
+            assert.equal(actualString, expectedString);
         });
     });
 
     describe('stringToUnicodeEscapeSequence (string: string, nonLatinAndNonDigitsOnly: boolean = false): string', () => {
-        const expected1: string = '\\x73\\x74\\x72\\x69\\x6e\\x67';
-        const expected2: string = 'abc\\x21\\u0434\\u0435';
+        describe('variant #1: default', () => {
+            const string: string = 'string';
+            const expectedString: string = '\\x73\\x74\\x72\\x69\\x6e\\x67';
+
+            let actualString: string;
 
-        it('should return a unicode escape sequence based on a given string', () => {
-            assert.equal(Utils.stringToUnicodeEscapeSequence('string'), expected1);
+            before(() => {
+                actualString = Utils.stringToUnicodeEscapeSequence(string);
+            });
+
+            it('should return a unicode escape sequence based on a given string', () => {
+                assert.equal(actualString, expectedString);
+            });
         });
 
-        it('should return a string where only non-digits and non-latin letters are escaped', () => {
-            assert.equal(Utils.stringToUnicodeEscapeSequence('abc!де', true), expected2);
+        describe('variant #2: escape non-digits and non-latin letters', () => {
+            const string: string = 'abc!де';
+            const expectedString: string = 'abc\\x21\\u0434\\u0435';
+
+            let actualString: string;
+
+            before(() => {
+                actualString = Utils.stringToUnicodeEscapeSequence(string, true);
+            });
+
+            it('should return a string where only non-digits and non-latin letters are escaped', () => {
+                assert.equal(actualString, expectedString);
+            });
         });
     });
 });

+ 44 - 49
yarn.lock

@@ -2,17 +2,17 @@
 # yarn lockfile v1
 
 
-"@types/chai@3.5.2":
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-3.5.2.tgz#c11cd2817d3a401b7ba0f5a420f35c56139b1c1e"
+"@types/chai@4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.0.0.tgz#4c9adabd2d04265769e6d9e847e86cc404dc7dcd"
 
 "@types/[email protected]":
   version "0.7.33"
   resolved "https://registry.yarnpkg.com/@types/chance/-/chance-0.7.33.tgz#9666ae5c8f602a14a67e5b608dabf1a7853a1e28"
 
-"@types/commander@2.3.31":
-  version "2.3.31"
-  resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.3.31.tgz#c60e5517091f9e2200a5e2063a830eddac4a7036"
+"@types/commander@2.9.1":
+  version "2.9.1"
+  resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.9.1.tgz#d4e464425baf4685bd49dd233be11de9c00c0784"
   dependencies:
     "@types/node" "*"
 
@@ -44,13 +44,13 @@
   version "2.2.41"
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.41.tgz#e27cf0817153eb9f2713b2d3f6c68f1e1c3ca608"
 
-"@types/node@*", "@types/[email protected]2":
-  version "7.0.22"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.22.tgz#4593f4d828bdd612929478ea40c67b4f403ca255"
+"@types/node@*", "@types/[email protected]9":
+  version "7.0.29"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.29.tgz#ccfcec5b7135c7caf6c4ffb8c7f33102340d99df"
 
-"@types/sinon@2.2.2":
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-2.2.2.tgz#a80da4868ba08accacbce4d37276f35e1ba0a52f"
+"@types/sinon@2.3.1":
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-2.3.1.tgz#5e214093e9e2345219ab0f31bf310c9790ad0712"
 
 "@types/[email protected]":
   version "1.0.2"
@@ -823,9 +823,9 @@ center-align@^0.1.1:
     align-text "^0.1.3"
     lazy-cache "^1.0.3"
 
[email protected].0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/chai/-/chai-4.0.0.tgz#f6c989e45a5707d40c54d97ddd7ca89b30a6a06a"
[email protected].2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/chai/-/chai-4.0.2.tgz#2f7327c4de6f385dd7787999e2ab02697a32b83b"
   dependencies:
     assertion-error "^1.0.1"
     check-error "^1.0.1"
@@ -1200,7 +1200,7 @@ escope@~1.0.1:
   dependencies:
     estraverse "^2.0.0"
 
-esmangle@^1.0.1:
[email protected]:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/esmangle/-/esmangle-1.0.1.tgz#d9bb37b8f8eafbf4e6d4ed6b7aa2956abbd3c4c2"
   dependencies:
@@ -2326,7 +2326,7 @@ onetime@^2.0.0:
   dependencies:
     mimic-fn "^1.0.0"
 
-opencollective@^1.0.3:
[email protected]:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/opencollective/-/opencollective-1.0.3.tgz#aee6372bc28144583690c3ca8daecfc120dd0ef1"
   dependencies:
@@ -2344,7 +2344,7 @@ [email protected]:
     object-assign "^4.0.1"
     pinkie-promise "^2.0.0"
 
-optimist@^0.6.1, optimist@~0.6.0:
+optimist@^0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
   dependencies:
@@ -2509,7 +2509,7 @@ pkg-dir@^1.0.0:
   dependencies:
     find-up "^1.0.0"
 
-pre-commit@^1.2.2:
[email protected]:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6"
   dependencies:
@@ -2844,9 +2844,9 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
 
[email protected].2:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.3.2.tgz#c43a9c570f32baac1159505cfeed19108855df89"
[email protected].3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.3.3.tgz#fc09e93451e32cb2d08cab16a03bea69778853fc"
   dependencies:
     diff "^3.1.0"
     formatio "1.2.0"
@@ -3082,9 +3082,9 @@ trim-right@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
 
[email protected].4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.0.4.tgz#a1475ebf24fd4e2ee2fba8b1aa1605b977bde506"
[email protected].6:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.0.6.tgz#55127ff790c7eebf6ba68c1e6dde94b09aaa21e0"
   dependencies:
     arrify "^1.0.0"
     chalk "^1.1.1"
@@ -3095,7 +3095,7 @@ [email protected]:
     source-map-support "^0.4.0"
     tsconfig "^6.0.0"
     v8flags "^2.0.11"
-    yn "^1.2.0"
+    yn "^2.0.0"
 
 tsconfig@^6.0.0:
   version "6.0.0"
@@ -3104,7 +3104,7 @@ tsconfig@^6.0.0:
     strip-bom "^3.0.0"
     strip-json-comments "^2.0.0"
 
[email protected], tslib@^1.6.0:
[email protected], tslib@^1.7.1:
   version "1.7.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec"
 
@@ -3118,23 +3118,24 @@ [email protected]:
     rimraf "^2.4.4"
     semver "^5.3.0"
 
-tslint@5.3.2:
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.3.2.tgz#e56459fb095a7307f103b84052174f5e3bbef6ed"
+tslint@5.4.3:
+  version "5.4.3"
+  resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.4.3.tgz#761c8402b80e347b7733a04390a757b253580467"
   dependencies:
     babel-code-frame "^6.22.0"
     colors "^1.1.2"
+    commander "^2.9.0"
     diff "^3.2.0"
     glob "^7.1.1"
-    optimist "~0.6.0"
+    minimatch "^3.0.4"
     resolve "^1.3.2"
     semver "^5.3.0"
-    tslib "^1.6.0"
-    tsutils "^2.0.0"
+    tslib "^1.7.1"
+    tsutils "^2.3.0"
 
-tsutils@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.1.0.tgz#5be8376c929528f65b302de9e17b0004e4c1eb20"
+tsutils@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.3.0.tgz#96e661d7c2363f31adc8992ac67bbe7b7fc175e5"
 
 [email protected]:
   version "0.0.0"
@@ -3172,9 +3173,9 @@ typedarray@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
[email protected].3:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.3.tgz#9639f3c3b40148e8ca97fe08a51dd1891bb6be22"
[email protected].4:
+  version "2.3.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42"
 
 uglify-js@^2.6, uglify-js@^2.8.27:
   version "2.8.27"
@@ -3312,7 +3313,7 @@ [email protected]:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
 
[email protected]:
[email protected], wordwrap@~0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
 
@@ -3320,10 +3321,6 @@ wordwrap@^1.0.0, wordwrap@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
 
-wordwrap@~0.0.2:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-
 wrap-ansi@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -3380,8 +3377,6 @@ yargs@~3.10.0:
     decamelize "^1.0.0"
     window-size "0.1.0"
 
-yn@^1.2.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/yn/-/yn-1.3.0.tgz#1b0812abb8d805d48966f8df385dc9dacc9a19d8"
-  dependencies:
-    object-assign "^4.1.1"
+yn@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a"

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff