/*global describe,it*/ 'use strict';

require('traceur');

var should = require('should'),

spider = require('../lib/spider');

function generateTest(code, expectation) {

return function () {
  should(spider.compile({ text: code, 
    generateSourceMap: false, 
    target: "ES6", 
    iifi: false, 
    useStrict: false 
  }).result).be.exactly(expectation);
};

}

function generateErrorTest(code, expectedErrors) {

return function () {
  var errors = spider.compile({ 
    text: code, 
    generateSourceMap: false, 
    target: "ES6", 
    iifi: false, 
    useStrict: false 
  }).errors;

  should(errors.map(function (error) {
    delete error.message;
    delete error.loc;

    return error;
  })).eql(expectedErrors);
};

}

describe('variable statement:', function () {

it('syntax error', generateErrorTest('var x = y^', [{ type: "SyntaxError" }]));

it('create variable', 
  generateTest('var a;', 'let a;'));

it('create variable with number literal', 
  generateTest('var a = 5;', 'let a = 5;'));

it('create variable with string literal', 
  generateTest('var a = "test";', 'let a = "test";'));

it('create variable with boolean literal', 
  generateTest('var a = true;', 'let a = true;'));

it('create variable with null literal', 
  generateTest('var a = null;', 'let a = null;'));

it('create variable with identifier value', 
  generateTest('var a = b;', 'let a = b;'));

it('create multiple variables in one statement', 
  generateTest('var a, b;', 'let a, b;'));

it('create multiple variables in one statement with values', 
  generateTest('var a = 5, b = false;', 'let a = 5, b = false;'));

it('create multiple variables in multiple statements', 
  generateTest('var a; var b;', 'let a;\nlet b;'));

});

describe('member expressions:', function () {

it('member expression with 2 nodes', 
  generateTest('var a = b.c;', 'let a = b.c;'));

it('member expression with 3 nodes', 
  generateTest('var a = b.c.d;', 'let a = b.c.d;'));

it('member expression with 4 nodes', 
  generateTest('var a = b.c.d.e;', 'let a = b.c.d.e;'));

});

describe('null propagating member expressions:', function () {

it('null propagating member expression with 2 nodes', 
  generateTest('var a = b?.c;', 'let a = typeof b !== "undefined" && b !== null ? b.c : void 0;'));

it('null propagating member expression with 3 nodes', 
  generateTest('var a = b?.c?.d;', 'let a = typeof b !== "undefined" && (b !== null && b.c !== null) ? b.c.d : void 0;'));

it('null propagating member expression with 4 nodes', 
  generateTest('var a = b?.c?.d?.e;', 'let a = typeof b !== "undefined" && (b !== null && b.c !== null && b.c.d !== null) ? b.c.d.e : void 0;'));

});

describe('member and null propagating member expressions:', function () {

it('1 member expression and 1 null propagating member expression', 
  generateTest('var a = b.c?.d;', 'let a = typeof b.c !== "undefined" && b.c !== null ? b.c.d : void 0;'));

it('2 member expressions and 1 null propagating member expression', 
  generateTest('var a = b.c.d?.e;', 'let a = typeof b.c.d !== "undefined" && b.c.d !== null ? b.c.d.e : void 0;'));

it('3 member expressions and 1 null propagating member expression', 
  generateTest('var a = b.c.d.e?.f;', 'let a = typeof b.c.d.e !== "undefined" && b.c.d.e !== null ? b.c.d.e.f : void 0;'));

it('1 member expression and 2 null propagating member expressions', 
  generateTest('var a = b.c?.d?.e;', 'let a = typeof b.c !== "undefined" && (b.c !== null && b.c.d !== null) ? b.c.d.e : void 0;'));

it('1 member expression and 3 null propagating member expressions', 
  generateTest('var a = b.c?.d?.e?.f;', 'let a = typeof b.c !== "undefined" && (b.c !== null && b.c.d !== null && b.c.d.e !== null) ? b.c.d.e.f : void 0;'));

it('2 member expressions and 2 null propagating member expressions', 
  generateTest('var a = b.c.d?.e?.f;', 'let a = typeof b.c.d !== "undefined" && (b.c.d !== null && b.c.d.e !== null) ? b.c.d.e.f : void 0;'));

it('3 member expressions and 3 null propagating member expressions', 
  generateTest('var a = b.c.d.e?.f?.g?.h;', 'let a = typeof b.c.d.e !== \"undefined\" && (b.c.d.e !== null && b.c.d.e.f !== null && b.c.d.e.f.g !== null) ? b.c.d.e.f.g.h : void 0;'));

it('1 null propagating member expression and 1 member expression', 
  generateTest('var a = b?.c;', 'let a = typeof b !== \"undefined\" && b !== null ? b.c : void 0;')); 

it('2 null propagating member expressions and 1 member expression', 
  generateTest('var a = b?.c?.d.e;', 'let a = typeof b !== "undefined" && (b !== null && b.c !== null) ? b.c.d.e : void 0;')); 

it('3 null propagating member expressions and 1 member expression', 
  generateTest('var a = b?.c?.d?.e.f;', 'let a = typeof b !== "undefined" && (b !== null && b.c !== null && b.c.d !== null) ? b.c.d.e.f : void 0;'));

it('1 null propagating member expression and 2 member expressions', 
  generateTest('var a = b?.c.d;', 'let a = typeof b !== "undefined" && b !== null ? b.c.d : void 0;'));

it('1 null propagating member expression and 3 member expressions', 
  generateTest('var a = b?.c.d.e;', 'let a = typeof b !== "undefined" && b !== null ? b.c.d.e : void 0;'));

it('2 null propagating member expressions and 2 member expressions', 
  generateTest('var a = b?.c?.d.e.f;', 'let a = typeof b !== "undefined" && (b !== null && b.c !== null) ? b.c.d.e.f : void 0;'));

it('3 null propagating member expression and 2 member expressions', 
  generateTest('var a = b?.c?.d?.e.f.g;', 'let a = typeof b !== "undefined" && (b !== null && b.c !== null && b.c.d !== null) ? b.c.d.e.f.g : void 0;'));

it('3 null propagating member expression and 3 member expressions', 
  generateTest('var a = b?.c?.d?.e.f.g.h;', 'let a = typeof b !== "undefined" && (b !== null && b.c !== null && b.c.d !== null) ? b.c.d.e.f.g.h : void 0;'));

it('1 member expression, 1 null propagating member expression, 1 member expression', 
  generateTest('var a = b.c?.d.e;', 'let a = typeof b.c !== "undefined" && b.c !== null ? b.c.d.e : void 0;'));

it('2 member expressions, 1 null propagating member expression, 1 member expression', 
  generateTest('var a = b.c.d?.e.f;', 'let a = typeof b.c.d !== "undefined" && b.c.d !== null ? b.c.d.e.f : void 0;')); 

it('1 member expression, 2 null propagating member expressions, 1 member expression', 
  generateTest('var a = b.c?.d?.e.f;', 'let a = typeof b.c !== \"undefined\" && (b.c !== null && b.c.d !== null) ? b.c.d.e.f : void 0;'));

it('1 member expression, 1 null propagating member expression, 2 member expressions', 
  generateTest('var a = b.c?.d.e.f;', 'let a = typeof b.c !== "undefined" && b.c !== null ? b.c.d.e.f : void 0;'));

it('1 null propagating member expression, 1 member expression, 1 null propagating member expression', 
  generateTest('var a = b?.c.d?.e;', 'let nullPropagating0 = typeof b !== \"undefined\" && b !== null ? b.c.d : void 0;\nlet a = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.e : void 0;'));

it('2 null propagating member expressions, 1 member expression, 1 null propagating member expression', 
  generateTest('var a = b?.c?.d.e?.f;', 'let nullPropagating0 = typeof b !== \"undefined\" && (b !== null && b.c !== null) ? b.c.d.e : void 0;\nlet a = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.f : void 0;'));

it('3 null propagating member expressions, 1 member expression, 1 null propagating member expression', 
  generateTest('var a = b?.c?.d?.e.f?.h;', 'let nullPropagating0 = typeof b !== \"undefined\" && (b !== null && b.c !== null && b.c.d !== null) ? b.c.d.e.f : void 0;\nlet a = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.h : void 0;'));

it('1 null propagating member expression, 2 member expressions, 1 null propagating member expression', 
  generateTest('var a = b?.c.d.e?.f;', 'let nullPropagating0 = typeof b !== \"undefined\" && b !== null ? b.c.d.e : void 0;\nlet a = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.f : void 0;'));

it('1 null propagating member expression, 3 member expressions, 1 null propagating member expression', 
  generateTest('var a = b?.c.d.e.f?.h;', 'let nullPropagating0 = typeof b !== \"undefined\" && b !== null ? b.c.d.e.f : void 0;\nlet a = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.h : void 0;'));

it('1 null propagating member expression, 1 member expression, 2 null propagating member expressions', 
  generateTest('var a = b?.c.d?.e?.f;', 'let nullPropagating0 = typeof b !== \"undefined\" && b !== null ? b.c.d : void 0;\nlet a = typeof nullPropagating0 !== \"undefined\" && (nullPropagating0 !== null && nullPropagating0.e !== null) ? nullPropagating0.e.f : void 0;'));      

it('1 null propagating member expression, 1 member expression, 3 null propagating member expressions', 
  generateTest('var a = b?.c.d?.e?.f?.h;', 'let nullPropagating0 = typeof b !== \"undefined\" && b !== null ? b.c.d : void 0;\nlet a = typeof nullPropagating0 !== \"undefined\" && (nullPropagating0 !== null && nullPropagating0.e !== null && nullPropagating0.e.f !== null) ? nullPropagating0.e.f.h : void 0;'));        

it('scramble member and null propagating member expressions (1)',
  generateTest('var a = b?.c.d?.e.f;', 'let nullPropagating0 = typeof b !== \"undefined\" && b !== null ? b.c.d : void 0;\nlet a = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.e.f : void 0;'));

it('scramble member and null propagating member expressions (2)',
  generateTest('var a = b?.c?.d.e?.f?.g.h?.i;', 'let nullPropagating0 = typeof b !== \"undefined\" && (b !== null && b.c !== null) ? b.c.d.e : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && (nullPropagating0 !== null && nullPropagating0.f !== null) ? nullPropagating0.f.g.h : void 0;\nlet a = typeof nullPropagating1 !== \"undefined\" && nullPropagating1 !== null ? nullPropagating1.i : void 0;'));

});

describe('call expressions and statements:', function () {

it('call statement without arguments',
  generateTest('f();', 'f();'));

it('call statement with 1 argument',
  generateTest('f(1);', 'f(1);'));

it('call statement with 2 arguments',
  generateTest('f(1, true);', 
    'f(1, true);'));

it('call statement with 3 arguments',
  generateTest('f(1, true, "test");', 
    'f(1, true, "test");'));

it('call statement with 4 arguments',
  generateTest('f(1, true, "test", { a: 1 });', 
    'f(1, true, "test", { a: 1 });'));

it('call expression without arguments',
  generateTest('var a = f();', 'let a = f();'));

it('call expression with 1 argument',
  generateTest('var a = f(1);', 'let a = f(1);'));

it('call expression with 2 arguments',
  generateTest('var a = f(1, true);', 'let a = f(1, true);'));

it('call expression with 3 arguments',
  generateTest('var a = f(1, true, "test");', 'let a = f(1, true, "test");'));

it('call expression with 4 arguments',
  generateTest('var a = f(1, true, "test", { a: 1 });', 'let a = f(1, true, "test", { a: 1 });'));

});

describe('call statements with member expressions:', function () {

it('1 member expression and 1 call statement', 
  generateTest('a.f();', 'a.f();'));

it('2 member expressions and 1 call statement', 
  generateTest('a.b.f();', 'a.b.f();'));

it('3 member expressions and 1 call statement', 
  generateTest('a.b.c.f();', 'a.b.c.f();'));  

it('1 member expression and 2 call statements', 
  generateTest('a.fn1().fn2();', 'a.fn1().fn2();'));

it('1 member expression and 3 call statements', 
  generateTest('a.fn1().fn2().fn3();', 'a.fn1().fn2().fn3();'));

it('1 member expression, 1 call statement, 1 member expression, 1 call statement', 
  generateTest('a.fn1().b.fn2();', 'a.fn1().b.fn2();'));

it('2 member expression, 2 call statement, 2 member expression, 2 call statement', 
  generateTest('a.b.fn1().fn2().c.d.fn3().fn4();', 'a.b.fn1().fn2().c.d.fn3().fn4();'));

});

describe('call statements with null propagating member expressions:', function () {

it('1 null propagating member expression and 1 call statement', 
  generateTest('a?.f();', 'if (typeof a !== \"undefined\" && a !== null) {\n    a.f();\n}'));

it('2 null propagating member expressions and 1 call statement', 
  generateTest('a?.b?.f();', 
    'if (typeof a !== \"undefined\" && (a !== null && a.b !== null)) {\n    a.b.f();\n}'));

it('3 null propagating member expressions and 1 call statement', 
  generateTest('a?.b?.c?.f();', 
    'if (typeof a !== \"undefined\" && (a !== null && a.b !== null && a.b.c !== null)) {\n    a.b.c.f();\n}'));  

it('1 null propagating member expression and 2 call statements', 
  generateTest('a?.fn1()?.fn2();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.fn1() : void 0;\nif (typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null) {\n    nullPropagating0.fn2();\n}'));

it('1 null propagating member expression and 3 call statements', 
  generateTest('a?.fn1()?.fn2()?.fn3();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.fn1() : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.fn2() : void 0;\nif (typeof nullPropagating1 !== \"undefined\" && nullPropagating1 !== null) {\n    nullPropagating1.fn3();\n}'));

it('1 null propagating member expression, 1 call statement, 1 null propagating member expression, 1 call statement', 
  generateTest('a?.fn1()?.b?.fn2();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.fn1() : void 0;\nif (typeof nullPropagating0 !== \"undefined\" && (nullPropagating0 !== null && nullPropagating0.b !== null)) {\n    nullPropagating0.b.fn2();\n}'));

it('2 null propagating member expression, 2 call statement, 2 null propagating member expression, 2 call statement', 
  generateTest('a?.b?.fn1()?.fn2()?.c?.d?.fn3()?.fn4();', 'let nullPropagating0 = typeof a !== \"undefined\" && (a !== null && a.b !== null) ? a.b.fn1() : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.fn2() : void 0;\nlet nullPropagating2 = typeof nullPropagating1 !== \"undefined\" && (nullPropagating1 !== null && nullPropagating1.c !== null && nullPropagating1.c.d !== null) ? nullPropagating1.c.d.fn3() : void 0;\nif (typeof nullPropagating2 !== \"undefined\" && nullPropagating2 !== null) {\n    nullPropagating2.fn4();\n}'));

});

describe('call statements with member and null propagating member expressions:', function () {

it('1 member expression, 1 call statement, 1 null propagating member expression, 1 call statement',
  generateTest('a.b()?.c();', 'let nullPropagating0 = a.b();\nif (typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null) {\n    nullPropagating0.c();\n}'));

it('2 member expressions, 1 call statement, 1 null propagating member expression, 1 call statement',
  generateTest('a.b.c()?.d();', 'let nullPropagating0 = a.b.c();\nif (typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null) {\n    nullPropagating0.d();\n}'));        

it('1 member expression, 2 call statements, 1 null propagating member expression, 1 call statement',
  generateTest('a.b().c()?.d();', 'let nullPropagating0 = a.b().c();\nif (typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null) {\n    nullPropagating0.d();\n}'));

it('1 member expression, 1 call statement, 2 null propagating member expressions, 1 call statement',
  generateTest('a.b()?.c?.d();', 'let nullPropagating0 = a.b();\nif (typeof nullPropagating0 !== \"undefined\" && (nullPropagating0 !== null && nullPropagating0.c !== null)) {\n    nullPropagating0.c.d();\n}'));

it('1 member expression, 1 call statement, 1 null propagating member expression, 2 call statement',
  generateTest('a.b()?.c().d();', 'let nullPropagating0 = a.b();\nif (typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null) {\n    nullPropagating0.c().d();\n}'));

it('1 null propagating member expression, 1 call statement, 1 member expression, 1 call statement',
  generateTest('a?.b().c();', 
    'if (typeof a !== \"undefined\" && a !== null) {\n    a.b().c();\n}'));

it('2 null propagating member expression, 1 call statement, 1 member expression, 1 call statement',
  generateTest('a?.b()?.c().d();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.b() : void 0;\nif (typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null) {\n    nullPropagating0.c().d();\n}'));

it('1 null propagating member expression, 2 call statement, 1 member expression, 1 call statement',
  generateTest('a?.b()?.c.d();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.b() : void 0;\nif (typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null) {\n    nullPropagating0.c.d();\n}'));        

it('scramble call statements with member and null propagating member expressions',
  generateTest('a?.b()?.c.d().e.f.g?.h?.i?.j.k();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.b() : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.c.d().e.f.g : void 0;\nif (typeof nullPropagating1 !== \"undefined\" && (nullPropagating1 !== null && nullPropagating1.h !== null && nullPropagating1.h.i !== null)) {\n    nullPropagating1.h.i.j.k();\n}'));

});

describe('call expressions with member expressions:', function () {

it('1 member expression and 1 call expression', 
  generateTest('var x = a.f();', 'let x = a.f();'));

it('2 member expressions and 1 call expression', 
  generateTest('var x = a.b.f();', 'let x = a.b.f();'));

it('3 member expressions and 1 call expression', 
  generateTest('var x = a.b.c.f();', 'let x = a.b.c.f();'));  

it('1 member expression and 2 call expressions', 
  generateTest('var x = a.fn1().fn2();', 'let x = a.fn1().fn2();'));

it('1 member expression and 3 call expressions', 
  generateTest('var x = a.fn1().fn2().fn3();', 'let x = a.fn1().fn2().fn3();'));

it('1 member expression, 1 call expression, 1 member expression, 1 call expression', 
  generateTest('var x = a.fn1().b.fn2();', 'let x = a.fn1().b.fn2();'));

it('2 member expression, 2 call expression, 2 member expression, 2 call expression', 
  generateTest('var x = a.b.fn1().fn2().c.d.fn3().fn4();', 'let x = a.b.fn1().fn2().c.d.fn3().fn4();'));

});

describe('call expressions with null propagating member expressions:', function () {

it('1 null propagating member expression and 1 call expression', 
  generateTest('var x = a?.f();', 'let x = typeof a !== \"undefined\" && a !== null ? a.f() : void 0;'));

it('2 null propagating member expressions and 1 call expression', 
  generateTest('var x = a?.b?.f();', 'let x = typeof a !== \"undefined\" && (a !== null && a.b !== null) ? a.b.f() : void 0;'));

it('3 null propagating member expressions and 1 call expression', 
  generateTest('var x = a?.b?.c?.f();', 'let x = typeof a !== \"undefined\" && (a !== null && a.b !== null && a.b.c !== null) ? a.b.c.f() : void 0;'));  

it('1 null propagating member expression and 2 call expressions', 
  generateTest('var x = a?.fn1()?.fn2();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.fn1() : void 0;\nlet x = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.fn2() : void 0;'));

it('1 null propagating member expression and 3 call expressions', 
  generateTest('var x = a?.fn1()?.fn2()?.fn3();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.fn1() : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.fn2() : void 0;\nlet x = typeof nullPropagating1 !== \"undefined\" && nullPropagating1 !== null ? nullPropagating1.fn3() : void 0;'));

it('1 null propagating member expression, 1 call expression, 1 null propagating member expression, 1 call expression', 
  generateTest('var x = a?.fn1()?.b?.fn2();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.fn1() : void 0;\nlet x = typeof nullPropagating0 !== \"undefined\" && (nullPropagating0 !== null && nullPropagating0.b !== null) ? nullPropagating0.b.fn2() : void 0;'));

it('2 null propagating member expression, 2 call expressions, 2 null propagating member expression, 2 call expressions', 
  generateTest('var x = a?.b?.fn1()?.fn2()?.c?.d?.fn3()?.fn4();', 'let nullPropagating0 = typeof a !== \"undefined\" && (a !== null && a.b !== null) ? a.b.fn1() : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.fn2() : void 0;\nlet nullPropagating2 = typeof nullPropagating1 !== \"undefined\" && (nullPropagating1 !== null && nullPropagating1.c !== null && nullPropagating1.c.d !== null) ? nullPropagating1.c.d.fn3() : void 0;\nlet x = typeof nullPropagating2 !== \"undefined\" && nullPropagating2 !== null ? nullPropagating2.fn4() : void 0;'));

});

describe('call expressions with member and null propagating member expressions:', function () {

it('1 member expression, 1 call expression, 1 null propagating member expression, 1 call expression',
  generateTest('var x = a.b()?.c();', 'let nullPropagating0 = a.b();\nlet x = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.c() : void 0;'));

it('2 member expressions, 1 call expression, 1 null propagating member expression, 1 call expression',
  generateTest('var x = a.b.c()?.d();', 'let nullPropagating0 = a.b.c();\nlet x = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.d() : void 0;'));        

it('1 member expression, 2 call expressions, 1 null propagating member expression, 1 call expression',
  generateTest('var x = a.b().c()?.d();', 'let nullPropagating0 = a.b().c();\nlet x = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.d() : void 0;'));

it('1 member expression, 1 call expression, 2 null propagating member expressions, 1 call expression',
  generateTest('var x = a.b()?.c?.d();', 'let nullPropagating0 = a.b();\nlet x = typeof nullPropagating0 !== \"undefined\" && (nullPropagating0 !== null && nullPropagating0.c !== null) ? nullPropagating0.c.d() : void 0;'));

it('1 member expression, 1 call expression, 1 null propagating member expression, 2 call expressions',
  generateTest('var x = a.b()?.c().d();', 'let nullPropagating0 = a.b();\nlet x = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.c().d() : void 0;'));

it('1 null propagating member expression, 1 call expression, 1 member expression, 1 call expression',
  generateTest('var x = a?.b().c();', 'let x = typeof a !== \"undefined\" && a !== null ? a.b().c() : void 0;'));

it('2 null propagating member expression, 1 call expression, 1 member expression, 1 call expression',
  generateTest('var x = a?.b()?.c().d();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.b() : void 0;\nlet x = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.c().d() : void 0;'));

it('1 null propagating member expression, 2 call expressions, 1 member expression, 1 call expression',
  generateTest('var x = a?.b()?.c.d();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.b() : void 0;\nlet x = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.c.d() : void 0;'));        

it('scramble call expressions with member and null propagating member expressions',
  generateTest('var x = a?.b()?.c.d().e.f.g?.h?.i?.j.k();', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.b() : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.c.d().e.f.g : void 0;\nlet x = typeof nullPropagating1 !== \"undefined\" && (nullPropagating1 !== null && nullPropagating1.h !== null && nullPropagating1.h.i !== null) ? nullPropagating1.h.i.j.k() : void 0;'));

});

describe('null coalescing expressions:', function () {

it('null coalescing expression with 2 identifiers',
  generateTest('var x = a ?? b;', 'let x = typeof a === \"undefined\" || a == null ? b : a;'));

it('null coalescing expression with 2 member expressions',
  generateTest('var x = a.b ?? c.d;', 'let x = a.b == null ? c.d : a.b;'));

it('null coalescing expression with 6 member expressions',
  generateTest('var x = a.b.c.d ?? e.f.g.h;', 'let x = a.b.c.d == null ? e.f.g.h : a.b.c.d;'));

it('null coalescing expression with 2 null propagating member expressions',
  generateTest('var x = a?.b ?? c?.d;', 'let nullCoalescing0 = typeof a !== \"undefined\" && a !== null ? a.b : void 0;\nlet x = nullCoalescing0 == null ? typeof c !== \"undefined\" && c !== null ? c.d : void 0 : nullCoalescing0;'));

it('null coalescing expression with 6 null propagating member expressions',
  generateTest('var x = a?.b?.c?.d ?? e?.f?.g?.h;', 'let nullCoalescing0 = typeof a !== \"undefined\" && (a !== null && a.b !== null && a.b.c !== null) ? a.b.c.d : void 0;\nlet x = nullCoalescing0 == null ? typeof e !== \"undefined\" && (e !== null && e.f !== null && e.f.g !== null) ? e.f.g.h : void 0 : nullCoalescing0;'));

it('null coalescing expression with 6 null propagating member expressions combined',
  generateTest('var x = a?.b.c?.d ?? e.f?.g?.h;', 'let nullPropagating0 = typeof a !== \"undefined\" && a !== null ? a.b.c : void 0;\nlet nullCoalescing0 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.d : void 0;\nlet x = nullCoalescing0 == null ? typeof e.f !== \"undefined\" && (e.f !== null && e.f.g !== null) ? e.f.g.h : void 0 : nullCoalescing0;'));       

it('null coalescing expression with 2 call expressions',
  generateTest('var x = a() ?? b();', 'let nullCoalescing0 = a();\nlet x = nullCoalescing0 == null ? b() : nullCoalescing0;'));

it('null coalescing expression with 2 call expressions and null propagating member expressions',
  generateTest('var x = a.b()?.c() ?? d?.e().f();', 'let nullPropagating0 = a.b();\nlet nullCoalescing0 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null ? nullPropagating0.c() : void 0;\nlet x = nullCoalescing0 == null ? typeof d !== \"undefined\" && d !== null ? d.e().f() : void 0 : nullCoalescing0;'));    

it('null coalescing statement',
  generateTest('a() ?? b();', 'let nullCoalescing0 = a();\nif (nullCoalescing0 == null) {\n    b();\n}'));

});

describe('null check call expressions:', function () {

it('null check call expression',
  generateTest('var x = a?();', 'let x = typeof a === \"function\" ? a() : void 0;'));

it('null check call expression with 1 argument',
  generateTest('var a = f?(1);', 'let a = typeof f === \"function\" ? f(1) : void 0;'));

it('null check call expression with 2 arguments',
  generateTest('var a = f?(1, true);', 'let a = typeof f === \"function\" ? f(1, true) : void 0;'));

it('null check call expression with 3 arguments',
  generateTest('var a = f?(1, true, "test");', 'let a = typeof f === \"function\" ? f(1, true, \"test\") : void 0;'));

it('null check call expression with 4 arguments',
  generateTest('var a = f?(1, true, "test", { a: 1 });', 'let a = typeof f === \"function\" ? f(1, true, \"test\", { a: 1 }) : void 0;'));

it('call expression with null check call expression',
  generateTest('var x = a?()();', 'let x = (typeof a === \"function\" ? a() : void 0)();'));

it('null check call expression with null check call expression',
  generateTest('var x = a?()?();', 'let nullCheck0 = typeof a === \"function\" ? a() : void 0;\nlet x = typeof nullCheck0 === \"function\" ? nullCheck0() : void 0;'));

it('null check call expression with member expression',
  generateTest('var x = a?().b;', 'let x = typeof a === \"function\" ? a().b : void 0;'));

it('null check call expression with 2 member expressions',
  generateTest('var x = a?().b.c;', 'let x = typeof a === \"function\" ? a().b.c : void 0;'));

it('null check call expression with call expression',
  generateTest('var x = a?().b();', 'let x = typeof a === \"function\" ? a().b() : void 0;')); 

it('null check call expression with 2 call expressions',
  generateTest('var x = a?().b().c();', 'let x = typeof a === \"function\" ? a().b().c() : void 0;')); 

it('member expression with null check call expression',
  generateTest('var x = a.b?();', 'let x = typeof a.b === \"function\" ? a.b() : void 0;'));

it('2 member expressions with null check call expression',
  generateTest('var x = a.b.c?();', 'let x = typeof a.b.c === \"function\" ? a.b.c() : void 0;'));

it('call expression with null check call expression',
  generateTest('var x = a().b?();', 'let nullCheck0 = a().b;\nlet x = typeof nullCheck0 === \"function\" ? nullCheck0() : void 0;'));

it('2 call expression with null check call expression',
  generateTest('var x = a().b().c?();', 'let nullCheck0 = a().b().c;\nlet x = typeof nullCheck0 === \"function\" ? nullCheck0() : void 0;'));

it('2 null check call expressions',
  generateTest('var x = a?().b?();', 'let nullCheck0 = typeof a === \"function\" ? a().b : void 0;\nlet x = typeof nullCheck0 === \"function\" ? nullCheck0() : void 0;'));

it('3 null check call expressions',
  generateTest('var x = a?().b?().c?();', 'let nullCheck0 = typeof a === \"function\" ? a().b : void 0;\nlet nullCheck1 = typeof nullCheck0 === \"function\" ? nullCheck0().c : void 0;\nlet x = typeof nullCheck1 === \"function\" ? nullCheck1() : void 0;'));

it('2 null check call expressions with null propagating relationship',
  generateTest('var x = a?()?.b?();', 'let nullPropagating0 = typeof a === \"function\" ? a() : void 0;\nlet x = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null && typeof nullPropagating0.b === \"function\" ? nullPropagating0.b() : void 0;'));       

it('3 null check call expressions with null propagating relationship',
  generateTest('var x = a?()?.b?()?.c();', 'let nullPropagating0 = typeof a === \"function\" ? a() : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null && typeof nullPropagating0.b === \"function\" ? nullPropagating0.b() : void 0;\nlet x = typeof nullPropagating1 !== \"undefined\" && nullPropagating1 !== null ? nullPropagating1.c() : void 0;'));

});

describe('null check call statements:', function () {

it('null check call statement',
  generateTest('a?();', 
    'if (typeof a === \"function\") {\n    a();\n}'));

it('null check call statement with 1 argument',
  generateTest('f?(1);', 
    'if (typeof f === \"function\") {\n    f(1);\n}'));

it('null check call statement with 2 arguments',
  generateTest('f?(1, true);', 
    'if (typeof f === \"function\") {\n    f(1, true);\n}'));

it('null check call statement with 3 arguments',
  generateTest('f?(1, true, "test");', 
    'if (typeof f === \"function\") {\n    f(1, true, \"test\");\n}'));

it('null check call statement with 4 arguments',
  generateTest('f?(1, true, "test", { a: 1 });', 
    'if (typeof f === \"function\") {\n    f(1, true, \"test\", { a: 1 });\n}'));

it('call statement with null check call expression',
  generateTest('a?()();', 
    '(typeof a === \"function\" ? a() : void 0)();'));

it('null check call expression with null check call expression',
  generateTest('a?()?();', 'let nullCheck0 = typeof a === \"function\" ? a() : void 0;\nif (typeof nullCheck0 === \"function\") {\n    nullCheck0();\n}'));

it('null check call expression with call statement',
  generateTest('a?().b();', 
    'if (typeof a === \"function\") {\n    a().b();\n}')); 

it('null check call expression with 2 call statements',
  generateTest('a?().b().c();', 
    'if (typeof a === \"function\") {\n    a().b().c();\n}')); 

it('member expression with null check call statement',
  generateTest('a.b?();', 
    'if (typeof a.b === \"function\") {\n    a.b();\n}'));

it('2 member expressions with null check call statement',
  generateTest('a.b.c?();', 
    'if (typeof a.b.c === \"function\") {\n    a.b.c();\n}'));

it('call expression with null check call statement',
  generateTest('a().b?();', 'let nullCheck0 = a().b;\nif (typeof nullCheck0 === \"function\") {\n    nullCheck0();\n}'));

it('2 call expression with null check call statement',
  generateTest('a().b().c?();', 'let nullCheck0 = a().b().c;\nif (typeof nullCheck0 === \"function\") {\n    nullCheck0();\n}'));

it('2 null check call statements',
  generateTest('a?().b?();', 'let nullCheck0 = typeof a === \"function\" ? a().b : void 0;\nif (typeof nullCheck0 === \"function\") {\n    nullCheck0();\n}'));

it('3 null check call statements',
  generateTest('a?().b?().c?();', 'let nullCheck0 = typeof a === \"function\" ? a().b : void 0;\nlet nullCheck1 = typeof nullCheck0 === \"function\" ? nullCheck0().c : void 0;\nif (typeof nullCheck1 === \"function\") {\n    nullCheck1();\n}'));

it('2 null check call statements with null propagating relationship',
  generateTest('a?()?.b?();', 'let nullPropagating0 = typeof a === \"function\" ? a() : void 0;\nif (typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null && typeof nullPropagating0.b === \"function\") {\n    nullPropagating0.b();\n}'));

it('3 null check call statements with null propagating relationship',
  generateTest('a?()?.b?()?.c();', 'let nullPropagating0 = typeof a === \"function\" ? a() : void 0;\nlet nullPropagating1 = typeof nullPropagating0 !== \"undefined\" && nullPropagating0 !== null && typeof nullPropagating0.b === \"function\" ? nullPropagating0.b() : void 0;\nif (typeof nullPropagating1 !== \"undefined\" && nullPropagating1 !== null) {\n    nullPropagating1.c();\n}'));

});

describe('existential expressions:', function () {

it('existential opreator on identifier',
  generateTest('var x = a?;', 'let x = typeof a !== \"undefined\" && a !== null;'));

it('existential opreator on call expression',
  generateTest('var x = a()?;', 'let existential0 = a();\nlet x = typeof existential0 !== \"undefined\" && existential0 !== null;'));

it('existential opreator on null check call expression',
  generateTest('var x = a?()?;', 'let existential0 = typeof a === \"function\" ? a() : void 0;\nlet x = typeof existential0 !== \"undefined\" && existential0 !== null;'));     

it('existential opreator on member expression',
  generateTest('var x = a.b?;', 'let x = typeof a.b !== \"undefined\" && a.b !== null;'));

it('existential opreator on member expression with call expression',
  generateTest('var x = a.b()?;', 'let existential0 = a.b();\nlet x = typeof existential0 !== \"undefined\" && existential0 !== null;'));

it('existential opreator on member expression with null check call expression',
  generateTest('var x = a.b?()?;', 'let existential0 = typeof a.b === \"function\" ? a.b() : void 0;\nlet x = typeof existential0 !== \"undefined\" && existential0 !== null;'));

it('existential opreator on null propagating member expression',
  generateTest('var x = a?.b?;', 'let existential0 = typeof a !== \"undefined\" && a !== null ? a.b : void 0;\nlet x = typeof existential0 !== \"undefined\" && existential0 !== null;'));

it('existential opreator on propagating member expression with call expression',
  generateTest('var x = a?.b()?;', 'let existential0 = typeof a !== \"undefined\" && a !== null ? a.b() : void 0;\nlet x = typeof existential0 !== \"undefined\" && existential0 !== null;'));

it('existential opreator on propagating member expression with null check call expression',
  generateTest('var x = a?.b?()?;', 'let existential0 = typeof a !== \"undefined\" && a !== null && typeof a.b === \"function\" ? a.b() : void 0;\nlet x = typeof existential0 !== \"undefined\" && existential0 !== null;'));

});

describe('object expressions:', function () {

it('empty object',
  generateTest('var x = {};', 'let x = {};'));

it('object with number property',
  generateTest('var x = { a: 1 };', 'let x = { a: 1 };'));

it('object with boolean property',
  generateTest('var x = { a: true };', 'let x = { a: true };'));

it('object with identifier property',
  generateTest('var x = { a: b };', 'let x = { a: b };'));

it('object with string property',
  generateTest('var x = { a: "test" };', 'let x = { a: "test" };'));

it('object with object property',
  generateTest('var x = { a: { b: 1 } };', 'let x = { a: { b: 1 } };'));

it('object with array property',
  generateTest('var x = { a: [true] };', 'let x = { a: [true] };'));

it('object with quoted object property name',
  generateTest('var x = { "a b": { b: 1 } };', 'let x = { \"a b\": { b: 1 } };'));

it('object with 2 properties',
  generateTest('var x = { a: 1, b: true };', 'let x = {\n    a: 1,\n    b: true\n};')); 

it('object with 3 properties',
  generateTest('var x = { a: 1, b: true, c: b };', 'let x = {\n    a: 1,\n    b: true,\n    c: b\n};'));

it('object with 4 properties',
  generateTest('var x = { a: 1, b: true, c: b, d: "test" };', 'let x = {\n    a: 1,\n    b: true,\n    c: b,\n    d: "test"\n};'));

});

describe('array expressions:', function () {

it('empty array',
  generateTest('var x = [];', 'let x = [];'));

it('array with 1 number element',
  generateTest('var x = [1];', 'let x = [1];'));  

it('array with 2 number elements',
  generateTest('var x = [1, 2];', 'let x = [\n    1,\n    2\n];'));

it('array with 3 number elements',
  generateTest('var x = [1, 2, 3];', 'let x = [\n    1,\n    2,\n    3\n];'));

it('array with 1 boolean element',
  generateTest('var x = [true];', 'let x = [true];'));  

it('array with 2 boolean elements',
  generateTest('var x = [true, false];', 'let x = [\n    true,\n    false\n];'));

it('array with 3 boolean elements',
  generateTest('var x = [true, false, true];', 'let x = [\n    true,\n    false,\n    true\n];'));

it('array with 1 identifier element',
  generateTest('var x = [a];', 'let x = [a];'));  

it('array with 2 identifier elements',
  generateTest('var x = [a, b];', 'let x = [\n    a,\n    b\n];'));

it('array with 3 identifier elements',
  generateTest('var x = [a, b, c];', 'let x = [\n    a,\n    b,\n    c\n];'));

it('array with 1 string element',
  generateTest('var x = ["test"];', 'let x = ["test"];'));  

it('array with 2 string elements',
  generateTest('var x = ["test", "test2"];', 'let x = [\n    "test",\n    "test2"\n];'));

it('array with 3 string elements',
  generateTest('var x = ["test", "test2", "test3"];', 'let x = [\n    "test",\n    "test2",\n    "test3"\n];'));

it('array with 1 array element',
  generateTest('var x = [[true]];', 'let x = [[true]];'));  

it('array with 2 array elements',
  generateTest('var x = [[true], [false]];', 'let x = [\n    [true],\n    [false]\n];'));

it('array with 3 array elements',
  generateTest('var x = [[true], [false], [true]];', 'let x = [\n    [true],\n    [false],\n    [true]\n];'));

it('array with 1 object element',
  generateTest('var x = [{ a: 1 }];', 'let x = [{ a: 1 }];'));  

it('array with 2 object elements',
  generateTest('var x = [{ a: 1 }, { b: 2 }];', 'let x = [\n    { a: 1 },\n    { b: 2 }\n];'));

it('array with 3 object elements',
  generateTest('var x = [{ a: 1 }, { b: 2 }, { c: 3 }];', 'let x = [\n    { a: 1 },\n    { b: 2 },\n    { c: 3 }\n];'));

});

describe('unary expressions:', function () {

it('unary not',
  generateTest('var x = !a;', 'let x = !a;'));

it('unary plus',
  generateTest('var x = +5;', 'let x = +5;'));

it('unary minus',
  generateTest('var x = -5;', 'let x = -5;'));

it('typeof identifier',
  generateTest('var x = typeof a;', 'let x = typeof a === \"undefined\" ? \"undefined\" : {}.toString.call(a).match(/\\s([a-zA-Z]+)/)[1].toLowerCase();'));

it('typeof call expression',
  generateTest('var x = typeof a();', 'let x = {}.toString.call(a()).match(/\\s([a-zA-Z]+)/)[1].toLowerCase();'));

});

describe('arithmetic expressions:', function () {

it('plus with 2 elements',
  generateTest('var x = a+b;', 'let x = a + b;'));

it('plus with 3 elements',
  generateTest('var x = a+b+c;', 'let x = a + b + c;'));

it('minus with 2 elements',
  generateTest('var x = a-b;', 'let x = a - b;'));

it('minus with 3 elements',
  generateTest('var x = a-b-c;', 'let x = a - b - c;'));

it('multipication with 2 elements',
  generateTest('var x = a*b;', 'let x = a * b;'));

it('multipication with 3 elements',
  generateTest('var x = a*b*c;', 'let x = a * b * c;')); 

it('division with 2 elements',
  generateTest('var x = a/b;', 'let x = a / b;'));

it('division with 3 elements',
  generateTest('var x = a/b/c;', 'let x = a / b / c;'));

it('order of operators',
  generateTest('var x = 3+4*5-(3+4)*5/(a+b-c*d);', 'let x = 3 + 4 * 5 - (3 + 4) * 5 / (a + b - c * d);'));    

it('exponentiation with 2 elements',
  generateTest('var x = a**b;', 'let x = Math.pow(a, b);'));

it('exponentiation with 3 elements',
  generateTest('var x = a**b**c;', 'let x = Math.pow(Math.pow(a, b), c);'));

it('integer division with 2 elements',
  generateTest('var x = a#b;', 'let x = Math.floor(a / b);'));

it('integer division with 3 elements',
  generateTest('var x = a#b#c;', 'let x = Math.floor(Math.floor(a / b) / c);'));

it('modulo with 2 elements',
  generateTest('var x = a%b;', 'let x = a % b;'));

it('modulo with 3 elements',
  generateTest('var x = a%b%c;', 'let x = a % b % c;'));    

it('math modulo with 2 elements',
  generateTest('var x = a%%b;', 'let x = (a % b + b) % b;'));

it('math modulo with 3 elements',
  generateTest('var x = a%%b%%c;', 'let x = ((a % b + b) % b % c + c) % c;'));

});

describe('logical expressions:', function () {

it('AND expression',
  generateTest('var x = a && b;', 'let x = !!a && !!b;'));

it('2 AND expressions',
  generateTest('var x = a && b && c;', 'let x = !!(!!a && !!b) && !!c;'));

it('worded AND expression',
  generateTest('var x = a and b;', 'let x = !!a && !!b;'));

it('2 worded AND expressions',
  generateTest('var x = a and b and c;', 'let x = !!(!!a && !!b) && !!c;'));

it('OR expression',
  generateTest('var x = a || b;', 'let x = !!a || !!b;'));

it('2 OR expressions',
  generateTest('var x = a || b || c;', 'let x = !!(!!a || !!b) || !!c;'));

it('worded OR expression',
  generateTest('var x = a or b;', 'let x = !!a || !!b;'));

it('2 worded OR expressions',
  generateTest('var x = a or b or c;', 'let x = !!(!!a || !!b) || !!c;'));

it('order of logical expressions 1',
  generateTest('var x = a && b || c;', 'let x = !!(!!a && !!b) || !!c;'));

it('order of logical expressions 2',
  generateTest('var x = a && b || c && d;', 'let x = !!(!!a && !!b) || !!(!!c && !!d);'));

it('order of logical expressions 3',
  generateTest('var x = a && b || c && (d || e || f && g);', 'let x = !!(!!a && !!b) || !!(!!c && !!(!!(!!d || !!e) || !!(!!f && !!g)));'));

});

describe('binary expressions:', function () {

it('equals expression',
  generateTest('var x = a == b;', 'let x = a === b;'));

it('not equals expression',
  generateTest('var x = a != b;', 'let x = a !== b;'));

it('larger expression',
  generateTest('var x = a > b;', 'let x = a > b;'));

it('larger or equals expression',
  generateTest('var x = a >= b;', 'let x = a >= b;'));

it('smaller expression',
  generateTest('var x = a < b;', 'let x = a < b;'));

it('smaller or equals expression',
  generateTest('var x = a <= b;', 'let x = a <= b;'));

it('chained comparisons',
  generateTest('var x = a > x > c;', 'let x = a > x && x > c;'));

it('chained comparisons with 2 components',
  generateTest('var x = a > x > c > d;', 'let x = a > x && x > c && c > d;'));

it('chained comparisons with 3 components',
  generateTest('var x = a > x > c > d > e;', 'let x = a > x && x > c && c > d && d > e;'));

});

describe('update expressions:', function () {

it('increment statement',
  generateTest('a++;', 'a++;'));

it('decrement statement',
  generateTest('a--;', 'a--;'));

it('increment expression',
  generateTest('var x = a++;', 'let x = a++;'));

it('decrement expression',
  generateTest('var x = a--;', 'let x = a--;'));

it('increment prefix statement',
  generateTest('++a;', '++a;'));

it('decrement prefix statement',
  generateTest('--a;', '--a;'));

it('increment prefix expression',
  generateTest('var x = ++a;', 'let x = ++a;'));

it('decrement prefix expression',
  generateTest('var x = --a;', 'let x = --a;'));

});

describe('assignment expressions:', function () {

it('assignment statement',
  generateTest('a = 1;', 'a = 1;'));

it('assignment statement with 2 variables',
  generateTest('a = b = 1;', 'a = b = 1;'));

it('assignment statement with 3 variables',
  generateTest('a = b = c = 1;', 'a = b = c = 1;'));

it('assignment expression',
  generateTest('var x = a = 1;', 'let x = a = 1;'));

it('assignment expression with 2 variables',
  generateTest('var x = a = b = 1;', 'let x = a = b = 1;'));

it('assignment expression with 3 variables',
  generateTest('var x = a = b = c = 1;', 'let x = a = b = c = 1;'));  

it('addition assignment',            generateTest('x += a;',   'x += a;'));
it('subtraction assignment',         generateTest('x -= a;',   'x -= a;'));
it('multiplication assignment',      generateTest('x *= a;',   'x *= a;'));  
it('division assignment',            generateTest('x /= a;',   'x /= a;'));
it('remainder assignment',           generateTest('x %= a;',   'x %= a;'));
it('left shift assignment',          generateTest('x <<= a;',  'x <<= a;'));
it('right shift assignment',         generateTest('x >>= a;',  'x >>= a;'));
it('logical right shift assignment', generateTest('x >>>= a;', 'x >>>= a;'));
it('bitwise AND assignment',         generateTest('x &= a;',   'x &= a;'));
it('bitwise XOR assignment',         generateTest('x ^= a;',   'x ^= a;'));
it('bitwise OR assignment',          generateTest('x |= a;',   'x |= a;'));

});

describe('if statement:', function () {

it('if statement',
  generateTest('if true { }', 'if (true) {\n}'));

it('if statement with else clause',
  generateTest('if true { } else { }', 'if (true) {\n} else {\n}'));

it('if statement without block',
  generateTest('if true a();', 'if (true) {\n    a();\n}')); 

it('if statement with else without block',
  generateTest('if true a(); else b();', 'if (true) {\n    a();\n} else {\n    b();\n}'));

});

describe('for statement:', function () {

it('for loop with empty arguments',
  generateTest('for ;; { }', 'for (;;) {\n}'));

it('for loop with initialiser only',
  generateTest('for var i = 0;; { }', 'for (let i = 0;;) {\n}'));  

it('for loop with condition only',
  generateTest('for ; i > 0; { }', 'for (; i > 0;) {\n}'));

it('for loop with update only',
  generateTest('for ;; i++ { }', 'for (;; i++) {\n}'));

it('for loop with initialiser and condition',
  generateTest('for var i = 0; i > 0; { }', 'for (let i = 0; i > 0;) {\n}'));

it('for loop with initialiser and update',
  generateTest('for var i = 0;; i++ { }', 'for (let i = 0;; i++) {\n}'));

it('for loop with condition and update',
  generateTest('for ; i > 0; i++ { }', 'for (; i > 0; i++) {\n}'));

it('for loop with initialiser, condition and update',
  generateTest('for var i = 0; i > 0; i++ { }', 'for (let i = 0; i > 0; i++) {\n}'));

});

describe('function declarations:', function () {

it('fn decl without arguments',
  generateTest('fn f() { }', 'function f() {\n}'));

it('fn decl with 1 argument',
  generateTest('fn f(a) { }', 'function f(a) {\n}'));

it('fn decl with 2 arguments',
  generateTest('fn f(a, b) { }', 'function f(a, b) {\n}'));

it('fn decl with 3 arguments',
  generateTest('fn f(a, b, c) { }', 'function f(a, b, c) {\n}'));

});

describe('function expressions:', function () {

it('function expression without arguments',
  generateTest('var a = () -> 1;', 'let a = function () {\n    return 1;\n};'));

it('function expression with 1 argument',
  generateTest('var a = (a) -> a;', 'let a = function (a) {\n    return a;\n};'));

it('function expression with 2 arguments',
  generateTest('var a = (a, b) -> a+b;', 'let a = function (a, b) {\n    return a + b;\n};'));

it('function expression with 3 arguments',
  generateTest('var a = (a, b, c) -> a+b-c;', 'let a = function (a, b, c) {\n    return a + b - c;\n};')); 

it('block function expression without arguments',
  generateTest('var a = () -> { };', 'let a = function () {\n};'));

it('block function expression with 1 argument',
  generateTest('var a = (a) -> { };', 'let a = function (a) {\n};'));

it('block function expression with 2 arguments',
  generateTest('var a = (a, b) -> { };', 'let a = function (a, b) {\n};'));

it('block function expression with 3 arguments',
  generateTest('var a = (a, b, c) -> { };', 'let a = function (a, b, c) {\n};')); 

it('block function expression (fn syntax) without arguments',
  generateTest('var a = fn () { };', 'let a = function () {\n};'));

it('block function expression (fn syntax) with 1 argument',
  generateTest('var a = fn (a) { };', 'let a = function (a) {\n};'));

it('block function expression (fn syntax) with 2 arguments',
  generateTest('var a = fn (a, b) { };', 'let a = function (a, b) {\n};'));

it('block function expression (fn syntax) with 3 arguments',
  generateTest('var a = fn (a, b, c) { };', 'let a = function (a, b, c) {\n};'));

it('block function expression (fn syntax with id) without arguments',
  generateTest('var a = fn f() { };', 'let a = function f() {\n};'));

it('block function expression (fn syntax with id) with 1 argument',
  generateTest('var a = fn f(a) { };', 'let a = function f(a) {\n};'));

it('block function expression (fn syntax with id) with 2 arguments',
  generateTest('var a = fn f(a, b) { };', 'let a = function f(a, b) {\n};'));

it('block function expression (fn syntax with id) with 3 arguments',
  generateTest('var a = fn f(a, b, c) { };', 'let a = function f(a, b, c) {\n};'));

});

describe('return statement:', function () {

it('function declaration with a return statement',
  generateTest('fn x() { return 1; }',
    'function x() {\n    return 1;\n}'));

it('function expression with a return statement',
  generateTest('var a = () -> { return 1; };', 'let a = function () {\n    return 1;\n};'));

it('function declaration with empty return statement',
  generateTest('fn x() { return; }',
    'function x() {\n    return;\n}'));

it('function expression with empty return statement',
  generateTest('var a = () -> { return; };', 'let a = function () {\n    return;\n};'));

});

describe('global identifiers and use statements:', function () {

it('global function call', 
  generateErrorTest('x("test");', 
    [{ type: 'UndefinedIdentifier', 'identifier': 'x' }]));

it('global function call with :: operator', 
  generateTest('::x("test");', 'x("test");'));

it('global function call with use statement', 
  generateTest('use x; x("test");', 'x("test");'));

it('global function call with use statement and :: operator', 
  generateTest('use x; ::x("test");', 'x("test");'));

it('global function call with use statement after the function call', 
  generateErrorTest('x("test"); use x;', 
    [{ type: 'UndefinedIdentifier', 'identifier': 'x' }]));

it('global member expression', 
  generateErrorTest('var x = a.b;', 
    [{ type: 'UndefinedIdentifier', 'identifier': 'a' }]));

it('global member expression with :: operator', 
  generateTest('var x = ::a.b;', 'let x = a.b;'));

it('global member expression with use statement', 
  generateTest('use a; var x = a.b;', 'let x = a.b;'));

it('global member expression with use statement and :: operator', 
  generateTest('use a; var x = ::a.b;', 'let x = a.b;'));

it('global member expression with use statement after the member expression', 
  generateErrorTest('var x = a.b; use a;', 
    [{ type: 'UndefinedIdentifier', 'identifier': 'a' }]));

it('use statement with 2 identifiers', 
  generateErrorTest('use a, b; var x = a + b;', []));      

it('use statement with 3 identifiers', 
  generateErrorTest('use a, b, c; var x = a + b + c;', []));

it(':browser use statement', 
  generateErrorTest('use :browser; var x = document + console + window;', []));

});

describe('this keyword:', function () {

it('call statement with this', 
  generateTest('this.x();', 'this.x();'));
it('call expression with this', 
  generateTest('var a = this.x();', 'let a = this.x();'));
it('member expression with this', 
  generateTest('var a = this.x;', 'let a = this.x;'));
it('call statement and member expression with this', 
  generateTest('this.a.b();', 'this.a.b();'));

});

describe('fn extends:', function () {

it('extended function with no constructor', 
  generateTest('fn A() {} fn B() extends A {}', 
    'function A() {\n}\nfunction B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);'));

it('extended function with empty constructor', 
  generateTest('fn A() {} fn B() extends A() {}', 
    'function A() {\n}\nfunction B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);'));

it('extended function with 1-param constructor', 
  generateTest('fn A() {} fn B() extends A(1) {}', 
    'function A() {\n}\nfunction B() {\n    A.call(this, 1);\n}\nB.prototype = Object.create(A);'));

it('extended function with 2-params constructor', 
  generateTest('fn A() {} fn B(a) extends A(1, a) {}', 
    'function A() {\n}\nfunction B(a) {\n    A.call(this, 1, a);\n}\nB.prototype = Object.create(A);'));

it('extended function with global identifier', 
  generateTest('fn B() extends ::A {}', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);'));

it('extended function expression with no constructor', 
  generateTest('var A = fn () {}; var B = fn () extends A {};', 'let A = function () {\n};\nlet functionExpression0 = function () {\n    A.call(this);\n};\nfunctionExpression0.prototype = Object.create(A);\nlet B = functionExpression0;'));

it('extended function expression with empty constructor', 
  generateTest('var A = fn () {}; var b = fn () extends A() {};', 'let A = function () {\n};\nlet functionExpression0 = function () {\n    A.call(this);\n};\nfunctionExpression0.prototype = Object.create(A);\nlet b = functionExpression0;'));

it('extended function expression with 1-param constructor', 
  generateTest('var A = fn () {}; var B = fn () extends A(1) {};', 'let A = function () {\n};\nlet functionExpression0 = function () {\n    A.call(this, 1);\n};\nfunctionExpression0.prototype = Object.create(A);\nlet B = functionExpression0;'));

it('extended function expression with 2-params constructor', 
  generateTest('var A = fn () {}; var B = fn (a) extends A(1, a) {};', 'let A = function () {\n};\nlet functionExpression0 = function (a) {\n    A.call(this, 1, a);\n};\nfunctionExpression0.prototype = Object.create(A);\nlet B = functionExpression0;'));

it('extended function expression with global identifier', 
  generateTest('var B = fn () extends ::A {};', 'let functionExpression0 = function () {\n    A.call(this);\n};\nfunctionExpression0.prototype = Object.create(A);\nlet B = functionExpression0;'));

});

describe('super keyword:', function () {

it('super call statement in a method', 
  generateTest('fn B() extends ::A { this.test = fn () { super.test(); }; }', 
    'function B() {\n    A.call(this);\n    let _self = this;\n    let _test = this.test;\n    this.test = function () {\n        _test.call(_self);\n    };\n}\nB.prototype = Object.create(A);'));

it('super call statement in a method inside an anonymous function', 
  generateTest('fn B() extends ::A { this.test = fn () { (() -> { return super.test(); })(); }; }', 
    'function B() {\n    A.call(this);\n    let _self = this;\n    let _test = this.test;\n    this.test = function () {\n        (function () {\n            return _test.call(_self);\n        }());\n    };\n}\nB.prototype = Object.create(A);'));

it('super call statement in a method inside 2 anonymous functions', 
  generateTest('fn B() extends ::A { this.test = fn () { (() -> () -> { return super.test(); } )()(); }; }', 
    'function B() {\n    A.call(this);\n    let _self = this;\n    let _test = this.test;\n    this.test = function () {\n        (function () {\n            return function () {\n                return _test.call(_self);\n            };\n        }()());\n    };\n}\nB.prototype = Object.create(A);'));

it('super member expression in a method', 
  generateTest('fn B() extends ::A { this.test = fn () { var x = super.x; }; }', 
    'function B() {\n    A.call(this);\n    let _x = this.x;\n    this.test = function () {\n        let x = _x;\n    };\n}\nB.prototype = Object.create(A);'));

it('super member expression in a method inside an anonymous function', 
  generateTest('fn B() extends ::A { this.test = fn () { var x = (() -> super.x)(); }; }', 
    'function B() {\n    A.call(this);\n    let _x = this.x;\n    this.test = function () {\n        let x = function () {\n            return _x;\n        }();\n    };\n}\nB.prototype = Object.create(A);'));

it('super member expression in a method inside 2 anonymous functions', 
  generateTest('fn B() extends ::A { this.test = fn () { var x = (() -> () -> super.x)()(); }; }', 
    'function B() {\n    A.call(this);\n    let _x = this.x;\n    this.test = function () {\n        let x = function () {\n            return function () {\n                return _x;\n            };\n        }()();\n    };\n}\nB.prototype = Object.create(A);'));

it('super call statement in a prototype function', 
  generateTest('fn B() extends ::A {} B.prototype.test = () -> { super.test(); };', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);\nB.prototype.test = function () {\n    A.prototype.test.call(this);\n};'));

it('super call statement in a prototype function inside an anonymous function', 
  generateTest('fn B() extends ::A {} B.prototype.test = () -> { (() -> super.test())(); };', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);\nB.prototype.test = function () {\n    let _self = this;\n    (function () {\n        return A.prototype.test.call(_self);\n    }());\n};'));

it('super member expression in a prototype function', 
  generateTest('fn B() extends ::A {} B.prototype.test = () -> { var x = super.x; };', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);\nB.prototype.test = function () {\n    let x = this.x;\n};'));

it('super member expression in a prototype function inside an anonymous function', 
  generateTest('fn B() extends ::A {} B.prototype.test = () -> { var x = (() -> { return super.x; })(); };', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);\nB.prototype.test = function () {\n    let _self = this;\n    let x = function () {\n        return _self.x;\n    }();\n};'));

it('super call statement in a prototype object', 
  generateTest('fn B() extends ::A {} B.prototype = { test: () -> { super.test(); } };', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);\nB.prototype = {\n    test: function () {\n        A.prototype.test.call(this);\n    }\n};'));

it('super call statement in a prototype object inside an anonymous function', 
  generateTest('fn B() extends ::A {} B.prototype = { test: () -> { (() -> super.test())(); } };', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);\nB.prototype = {\n    test: function () {\n        let _self = this;\n        (function () {\n            return A.prototype.test.call(_self);\n        }());\n    }\n};'));

it('super member expression in a prototype object', 
  generateTest('fn B() extends ::A {} B.prototype = { test: () -> { var x = super.x; } };', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);\nB.prototype = {\n    test: function () {\n        let x = this.x;\n    }\n};'));

it('super member expression in a prototype object inside an anonymous function', 
  generateTest('fn B() extends ::A {} B.prototype = { test: () -> { var x = (() -> { return super.x; })(); } };', 
    'function B() {\n    A.call(this);\n}\nB.prototype = Object.create(A);\nB.prototype = {\n    test: function () {\n        let _self = this;\n        let x = function () {\n            return _self.x;\n        }();\n    }\n};'));

});

describe('string interpolation:', function () {

it('string interpolation', 
  generateTest('var x = "test \\(a) test \\(2+a) \\(Math.pow(2, a)) test test";', 'let x = \"test \" + a + \" test \" + (2 + a) + \" \" + Math.pow(2, a) + \" test test\";'));

});

describe('bitwise opreators:', function () {

it('bitwise AND', generateTest('var x = a & b;', 'let x = a & b;'));
it('bitwise OR',  generateTest('var x = a | b;', 'let x = a | b;'));
it('bitwise XOR', generateTest('var x = a ^ b;', 'let x = a ^ b;'));

it('arithmetic shift left',  generateTest('var x = a << b;', 'let x = a << b;'));  
it('arithmetic shift right', generateTest('var x = a >> b;', 'let x = a >> b;'));  

it('logical shift right', generateTest('var x = a >>> b;', 'let x = a >>> b;'));

});

describe('throw statement:', function () {

it('throw number', generateTest('throw 5;', 'throw 5;'));
it('throw object', generateTest('throw { message: "test" };', 'throw { message: "test" };'));

});

describe('control flow statements:', function () {

it('break statement', generateTest('break;', 'break;'));
it('continue statement', generateTest('continue;', 'continue;'));

});

describe('range expressions:', function () {

it('upward small inclusive numeric range expression',
  generateTest('var x = [1..5];', 'let x = [\n    1,\n    2,\n    3,\n    4,\n    5\n];'));

it('downward small inclusive numeric range expression',
  generateTest('var x = [5..1];', 'let x = [\n    5,\n    4,\n    3,\n    2,\n    1\n];'));

it('upward small exclusive numeric range expression',
  generateTest('var x = [1...5];', 'let x = [\n    1,\n    2,\n    3,\n    4\n];'));

it('downward small exclusive numeric range expression',
  generateTest('var x = [5...1];', 'let x = [\n    5,\n    4,\n    3,\n    2\n];'));

it('upward large inclusive numeric range expression',
  generateTest('var x = [1..25];', 'let x = function () {\n    let _results = [];\n    for (let _i = 1; _i <= 25; _i++) {\n        _results.push(_i);\n    }\n    return _results;\n}.apply(this);'));

it('downward large inclusive numeric range expression',
  generateTest('var x = [25..1];', 'let x = function () {\n    let _results = [];\n    for (let _i = 25; _i >= 1; _i--) {\n        _results.push(_i);\n    }\n    return _results;\n}.apply(this);'));

it('upward large exclusive numeric range expression',
  generateTest('var x = [1...25];', 'let x = function () {\n    let _results = [];\n    for (let _i = 1; _i < 25; _i++) {\n        _results.push(_i);\n    }\n    return _results;\n}.apply(this);'));

it('downward large exclusive numeric range expression',
  generateTest('var x = [25...1];', 'let x = function () {\n    let _results = [];\n    for (let _i = 25; _i > 1; _i--) {\n        _results.push(_i);\n    }\n    return _results;\n}.apply(this);'));    

it('inclusive identifiers range expression',
  generateTest('var x = [a..b];', 'let x = function () {\n    let _results = [];\n    for (let _i = a; a <= b ? _i <= b : _i >= b; a <= b ? _i++ : _i--) {\n        _results.push(_i);\n    }\n    return _results;\n}.apply(this);'));

it('exclusive identifiers range expression',
  generateTest('var x = [a...b];', 'let x = function () {\n    let _results = [];\n    for (let _i = a; a <= b ? _i < b : _i > b; a <= b ? _i++ : _i--) {\n        _results.push(_i);\n    }\n    return _results;\n}.apply(this);'));

it('inclusive call range expression',
  generateTest('var x = [a()..b()];', 'let x = function () {\n    let _results = [], _start = a(), _end = b();\n    for (let _i = _start; _start <= _end ? _i <= _end : _i >= _end; _start <= _end ? _i++ : _i--) {\n        _results.push(_i);\n    }\n    return _results;\n}.apply(this);'));

it('exclusive call range expression',
  generateTest('var x = [a()...b()];', 'let x = function () {\n    let _results = [], _start = a(), _end = b();\n    for (let _i = _start; _start <= _end ? _i < _end : _i > _end; _start <= _end ? _i++ : _i--) {\n        _results.push(_i);\n    }\n    return _results;\n}.apply(this);'));

});

describe('debugger statement:', function () {

it('debugger statement', generateTest('debugger;', 'debugger;'));

});

describe('while and until statements:', function () {

it('while statement', generateTest('while true {}', 'while (true) {\n}'));
it('while statement without block', generateTest('while true a();', 'while (true) {\n    a();\n}'));

it('until statement', generateTest('until true {}', 'while (!true) {\n}'));
it('until statement without block', generateTest('until true a();', 'while (!true) {\n    a();\n}'));

});

describe('array slicing:', function () {

it('inclusive array slice with empty from and to', 
  generateTest('var x = a[..];', 'let x = a.slice(0);'));

it('inclusive array slice with numeric from and empty to', 
  generateTest('var x = a[1..];', 'let x = a.slice(1);'));

it('inclusive array slice with empty from and numeric to', 
  generateTest('var x = a[..1];', 'let x = a.slice(0, 2);'));

it('inclusive array slice with numeric from and numeric to', 
  generateTest('var x = a[1..2];', 'let x = a.slice(1, 3);'));

it('inclusive array slice with identifier from and empty to', 
  generateTest('var x = a[c..];', 'let x = a.slice(c);'));

it('inclusive array slice with empty from and identifier to', 
  generateTest('var x = a[..c];', 'let x = a.slice(0, c + 1);'));

it('inclusive array slice with identifier from and identifier to', 
  generateTest('var x = a[c..d];', 'let x = a.slice(c, d + 1);'));

it('exclusive array slice with empty from and to', 
  generateTest('var x = a[...];', 'let x = a.slice(0);'));

it('exclusive array slice with numeric from and empty to', 
  generateTest('var x = a[1...];', 'let x = a.slice(1);'));

it('exclusive array slice with empty from and numeric to', 
  generateTest('var x = a[...1];', 'let x = a.slice(0, 1);'));

it('exclusive array slice with numeric from and numeric to', 
  generateTest('var x = a[1...2];', 'let x = a.slice(1, 2);'));

it('exclusive array slice with identifier from and empty to', 
  generateTest('var x = a[c...];', 'let x = a.slice(c);'));

it('exclusive array slice with empty from and identifier to', 
  generateTest('var x = a[...c];', 'let x = a.slice(0, c);'));

it('exclusive array slice with identifier from and identifier to', 
  generateTest('var x = a[c...d];', 'let x = a.slice(c, d);'));

});

describe('array splicing:', function () {

it('inclusive array splicing with empty from and to', 
  generateTest('a[..] = [x];', '[].splice.apply(a, [\n    0,\n    9000000000\n].concat([x]));'));

it('inclusive array splicing with numeric from and empty to', 
  generateTest('a[1..] = [x];', '[].splice.apply(a, [\n    1,\n    9000000000\n].concat([x]));'));

it('inclusive array splicing with empty from and numeric to', 
  generateTest('a[..3] = [x];', '[].splice.apply(a, [\n    0,\n    4\n].concat([x]));'));

it('inclusive array splicing with numeric from and numeric to', 
  generateTest('a[2..3] = [x];', '[].splice.apply(a, [\n    2,\n    2\n].concat([x]));'));

it('inclusive array splicing with identifier from and empty to', 
  generateTest('a[t..] = [x];', '[].splice.apply(a, [\n    t,\n    9000000000\n].concat([x]));'));

it('inclusive array splicing with empty from and numeric to', 
  generateTest('a[..v] = [x];', '[].splice.apply(a, [\n    0,\n    v + 1\n].concat([x]));'));

it('inclusive array splicing with numeric from and numeric to', 
  generateTest('a[t..v] = [x];', '[].splice.apply(a, [\n    t,\n    v - t + 1\n].concat([x]));'));  

it('exclusive array splicing with empty from and to', 
  generateTest('a[...] = [x];', '[].splice.apply(a, [\n    0,\n    9000000000\n].concat([x]));'));

it('exclusive array splicing with numeric from and empty to', 
  generateTest('a[1...] = [x];', '[].splice.apply(a, [\n    1,\n    9000000000\n].concat([x]));'));

it('exclusive array splicing with empty from and numeric to', 
  generateTest('a[...3] = [x];', '[].splice.apply(a, [\n    0,\n    3\n].concat([x]));'));

it('exclusive array splicing with numeric from and numeric to', 
  generateTest('a[2...3] = [x];', '[].splice.apply(a, [\n    2,\n    1\n].concat([x]));'));

it('exclusive array splicing with identifier from and empty to', 
  generateTest('a[t...] = [x];', '[].splice.apply(a, [\n    t,\n    9000000000\n].concat([x]));'));

it('exclusive array splicing with empty from and numeric to', 
  generateTest('a[...v] = [x];', '[].splice.apply(a, [\n    0,\n    v\n].concat([x]));'));

it('exclusive array splicing with numeric from and numeric to', 
  generateTest('a[t...v] = [x];', '[].splice.apply(a, [\n    t,\n    v - t\n].concat([x]));'));

});

describe('new expressions:', function () {

it('new expression with 0 arguments', 
  generateTest('var x = new A();', 'let x = new A();'));

it('new expression with 1 argument', 
  generateTest('var x = new A(1);', 'let x = new A(1);'));

it('new expression with 2 argument', 
  generateTest('var x = new A(1, b());', 'let x = new A(1, b());'));

});

describe('default fn param value:', function () {

it('fn decl with 1 param with default value',
  generateTest('fn f(a = 5) {}', 
    'function f(a = 5) {\n}'));

it('fn decl with 2 params with default values',
  generateTest('fn f(a = 5, b = 3) {}', 
    'function f(a = 5, b = 3) {\n}'));

it('fn decl with 2 params - first with default value and second without',
  generateTest('fn f(a = 5, b) {}', 
    'function f(a = 5, b) {\n}'));

it('fn decl with 2 params - second with default value and first without',
  generateTest('fn f(a, b = 3) {}', 
    'function f(a, b = 3) {\n}'));  

it('fn decl with 3 params with default values',
  generateTest('fn f(a = 5, b = 3, k = { test: 1 }) {}', 
    'function f(a = 5, b = 3, k = { test: 1 }) {\n}'));    

it('fn expression with 1 param with default value',
  generateTest('var x = (a = 5) -> a;', 'let x = function (a = 5) {\n    return a;\n};'));

it('fn expression with 2 params with default values',
  generateTest('var x = fn (a = 5, b = 3) {};', 'let x = function (a = 5, b = 3) {\n};'));

it('fn expression with 2 params - first with default value and second without',
  generateTest('var x = fn (a = 5, b) {};', 'let x = function (a = 5, b) {\n};'));

it('fn expression with 2 params - second with default value and first without',
  generateTest('var x = fn (a, b = 3) {};', 'let x = function (a, b = 3) {\n};'));  

it('fn expression with 3 params with default values',
  generateTest('var x = fn (a = 5, b = 3, k = { test: 1 }) {};', 'let x = function (a = 5, b = 3, k = { test: 1 }) {\n};'));

});

describe('splat in function declaration:', function () {

it('fn decl with splat',
  generateTest('fn f(a...) {}', 
    'function f() {\n    let __splat, a = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];\n}'));

it('fn decl with splat, parameter',
  generateTest('fn f(a..., b) {}', 
    'function f() {\n    let __splat, a = 2 <= arguments.length ? [].slice.call(arguments, 0, __splat = arguments.length - 1) : (__splat = 0, []), b = arguments[__splat++];\n}'));

it('fn decl with parameter, splat',
  generateTest('fn f(a, b...) {}', 
    'function f() {\n    let __splat, a = arguments[0], b = 2 <= arguments.length ? [].slice.call(arguments, 1) : [];\n}'));

it('fn decl with parameter, splat, parameter',
  generateTest('fn f(a, b..., c) {}', 
    'function f() {\n    let __splat, a = arguments[0], b = 3 <= arguments.length ? [].slice.call(arguments, 1, __splat = arguments.length - 1) : (__splat = 1, []), c = arguments[__splat++];\n}'));

it('fn decl with parameter, parameter, splat, parameter',
  generateTest('fn f(a, b, c..., d) {}', 
    'function f() {\n    let __splat, a = arguments[0], b = arguments[1], c = 4 <= arguments.length ? [].slice.call(arguments, 2, __splat = arguments.length - 1) : (__splat = 2, []), d = arguments[__splat++];\n}'));

it('fn decl with parameter, splat, parameter, parameter',
  generateTest('fn f(a, b..., c, d) {}', 
    'function f() {\n    let __splat, a = arguments[0], b = 4 <= arguments.length ? [].slice.call(arguments, 1, __splat = arguments.length - 2) : (__splat = 1, []), c = arguments[__splat++], d = arguments[__splat++];\n}'));    

it('multiple splats in fn decl',
  generateErrorTest('fn f(a..., b...) {}', [{ type: "MultipleSplatsDisallowed" }]));

});

describe('splat in call expressions:', function () {

it('call expression with a splat', 
  generateTest('f(a...);', 'f.apply(null, a);'));

it('call expression with splat, argument',
  generateTest('f(a..., b);', 'f.apply(null, [].slice.call(a).concat([b]));'));

it('call expression with argument, splat',
  generateTest('f(a, b...);', 'f.apply(null, [a].concat([].slice.call(b)));'));

it('call expression with splat, argument, argument',
  generateTest('f(a..., b, c);', 'f.apply(null, [].slice.call(a).concat([b], [c]));'));

it('call expression with argument, splat, argument',
  generateTest('f(a, b..., c);', 'f.apply(null, [a].concat([].slice.call(b), [c]));'));

it('call expression with argument, argument, splat, argument',
  generateTest('f(a, b, c..., d);', 'f.apply(null, [\n    a,\n    b\n].concat([].slice.call(c), [d]));'));

it('null check call expression with a splat', 
  generateTest('f?(a...);', 'if (typeof f === \"function\") {\n    f.apply(null, a);\n}'));

it('null check call expression with splat, argument',
  generateTest('f?(a..., b);', 'if (typeof f === \"function\") {\n    f.apply(null, [].slice.call(a).concat([b]));\n}'));

it('null check call expression with argument, splat',
  generateTest('f?(a, b...);', 'if (typeof f === \"function\") {\n    f.apply(null, [a].concat([].slice.call(b)));\n}'));

it('null check call expression with splat, argument, argument',
  generateTest('f?(a..., b, c);', 'if (typeof f === \"function\") {\n    f.apply(null, [].slice.call(a).concat([b], [c]));\n}'));

it('null check call expression with argument, splat, argument',
  generateTest('f?(a, b..., c);', 'if (typeof f === \"function\") {\n    f.apply(null, [a].concat([].slice.call(b), [c]));\n}'));

it('null check call expression with argument, argument, splat, argument',
  generateTest('f?(a, b, c..., d);', 'if (typeof f === \"function\") {\n    f.apply(null, [\n        a,\n        b\n    ].concat([].slice.call(c), [d]));\n}'));

});

describe('conditional expressions:', function () {

it('conditional expression',
  generateTest('var x = a() if b else c();', 'let x = b ? a() : c();'));

});

describe('for in statement:', function () {

it('for in statement without index',
  generateTest('for item in array {}', 
    'for (let item of array) {\n}'));

it('for in statement with index',
  generateTest('for item, index in array {}', 
    'let index = 0;\nfor (let item of array) {\n    index++;\n}'));

});

describe('for of statement:', function () {

it('for of statement without value',
  generateTest('for key of object {}', 
    'for (let key of Object.keys(object)) {\n}'));

it('for of statement with value',
  generateTest('for key, value of object {}', 
    'for (let key of Object.keys(object)) {\n    let value = object[key];\n}'));  

it('for of statement of call expression without value',
  generateTest('for key of f() {}', 
    'for (let key of Object.keys(f())) {\n}'));

it('for of statement of call expression  with value',
  generateTest('for key, value of f() {}', 
    'let forOf0 = f();\nfor (let key of Object.keys(forOf0)) {\n    let value = forOf0[key];\n}'));

});

describe('try statement:', function () {

it('try statement without handler with finalizer', 
  generateTest('try { a(); } finally { b(); }', 
    'try {\n    a();\n} finally {\n    b();\n}'));

it('try statement with handler without finalizer', 
  generateTest('try { a(); } catch error { b(); }', 
    'try {\n    a();\n} catch (error) {\n    b();\n}'));

it('try statement with handler and finalizer', 
  generateTest('try { a(); } catch error { b(); } finally { c(); }', 
    'try {\n    a();\n} catch (error) {\n    b();\n} finally {\n    c();\n}'));

});

describe('in expression:', function () {

it('in expression with identifier',
  generateTest('var x = a in b;', 'let x = b instanceof Array ? b.indexOf(a) !== -1 : a in b;'));

it('in expression with call expression',
  generateTest('var x = a in b();', 'let inExpression0 = b();\nlet x = inExpression0 instanceof Array ? inExpression0.indexOf(a) !== -1 : a in inExpression0;'));

});

describe('list comprehensions:', function () {

 it('list comprehension without condition and without index', 
   generateTest('var x = [item + 1 for item in array];', 
     'let x = function () {\n    let forIn0 = [];\n    for (let item of array) {\n        forIn0.push(item + 1);\n    }\n    return forIn0;\n}();'));

 it('list comprehension with condition and without index', 
   generateTest('var x = [item + 1 for item in array if condition];', 
     'let x = function () {\n    let forIn0 = [];\n    for (let item of array) {\n        if (condition) {\n            forIn0.push(item + 1);\n        }\n    }\n    return forIn0;\n}();'));

 it('list comprehension without condition and with index', 
   generateTest('var x = [item + index for item, index in array];', 
     'let x = function () {\n    let forIn0 = [];\n    let index = 0;\n    for (let item of array) {\n        forIn0.push(item + index);\n        index++;\n    }\n    return forIn0;\n}();'));

it('list comprehension with condition and with index', 
   generateTest('var x = [item + index for item, index in array if index > 1];', 
     'let x = function () {\n    let forIn0 = [];\n    let index = 0;\n    for (let item of array) {\n        if (index > 1) {\n            forIn0.push(item + index);\n        }\n        index++;\n    }\n    return forIn0;\n}();'));

});

describe('switch statement:', function () {

it('switch statement with single case', 
  generateTest('switch n { case 1: { } }', 
    'if (n === 1) {\n}'));

it('switch statement with 2 cases', 
  generateTest('switch n { case 1: { }, case 2: { } }', 
    'if (n === 1) {\n} else if (n === 2) {\n}'));

it('switch statement with 2 cases with multiple options', 
  generateTest('switch n { case 1, 2: { }, case 3, 4: { } }', 
    'if (n === 1 || n === 2) {\n} else if (n === 3 || n === 4) {\n}'));

it('switch statement with 2 cases with multiple options and default case', 
  generateTest('switch n { case 1, 2: { }, case 3, 4: { }, default: { } }', 
    'if (n === 1 || n === 2) {\n} else if (n === 3 || n === 4) {\n} else {\n}'));

it('switch statement with 2 cases with multiple options, inclusive range case and default case', 
  generateTest('switch n { case 1, 2: { }, case 3, 4: { }, case 5..6: { }, default: { } }', 
    'if (n === 1 || n === 2) {\n} else if (n === 3 || n === 4) {\n} else if (n >= 5 && n <= 6) {\n} else {\n}'));

it('switch statement with 2 cases with multiple options, inclusive range case without from and default case', 
  generateTest('switch n { case 1, 2: { }, case 3, 4: { }, case ..6: { }, default: { } }', 
    'if (n === 1 || n === 2) {\n} else if (n === 3 || n === 4) {\n} else if (n <= 6) {\n} else {\n}'));

it('switch statement with 2 cases with multiple options, inclusive range case without to and default case', 
  generateTest('switch n { case 1, 2: { }, case 3, 4: { }, case 5..: { }, default: { } }', 
    'if (n === 1 || n === 2) {\n} else if (n === 3 || n === 4) {\n} else if (n >= 5) {\n} else {\n}'));

it('switch statement with 2 cases with multiple options, exclusive range case and default case', 
  generateTest('switch n { case 1, 2: { }, case 3, 4: { }, case 5...6: { }, default: { } }', 
    'if (n === 1 || n === 2) {\n} else if (n === 3 || n === 4) {\n} else if (n >= 5 && n < 6) {\n} else {\n}')); 

it('switch statement with 2 cases with multiple options, exclusive range case without from and default case', 
  generateTest('switch n { case 1, 2: { }, case 3, 4: { }, case ...6: { }, default: { } }', 
    'if (n === 1 || n === 2) {\n} else if (n === 3 || n === 4) {\n} else if (n < 6) {\n} else {\n}'));

it('switch statement with 2 cases with multiple options, exclusive range case without to and default case', 
  generateTest('switch n { case 1, 2: { }, case 3, 4: { }, case 5...: { }, default: { } }', 
    'if (n === 1 || n === 2) {\n} else if (n === 3 || n === 4) {\n} else if (n >= 5) {\n} else {\n}'));

it('switch statement with single default case', 
  generateErrorTest('switch ::n { default: { } }', 
    [{ "type": "SingleDefaultClause" }]));

it('switch statement with range case without from and without to', 
  generateErrorTest('switch ::n { case ..: { } }', 
    [{ "type": "EmptyRange" }]));

});

describe('fallthrough statement:', function () {

it('switch with 2 fallthrough cases', 
  generateTest('switch n { case 1..2: { a(); fallthrough; }, case 1..4: { b(); fallthrough; } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 4)) {\n    fallthrough0 = 2;\n    b();\n    fallthrough0 = 1;\n}'));

it('switch with 3 fallthrough cases', 
  generateTest('switch n { case 1..2: { a(); fallthrough; }, case 1..4: { b(); fallthrough; }, case 1..6: { c(); fallthrough; } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 4)) {\n    fallthrough0 = 2;\n    b();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 6)) {\n    fallthrough0 = 2;\n    c();\n    fallthrough0 = 1;\n}'));

it('switch with fallthrough-normal-fallthrough cases', 
  generateTest('switch n { case 1..2: { a(); fallthrough; }, case 1..4: { b(); }, case 1..6: { c(); fallthrough; } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 4)) {\n    fallthrough0 = 2;\n    b();\n} else if (fallthrough0 < 2 && (n >= 1 && n <= 6)) {\n    fallthrough0 = 2;\n    c();\n    fallthrough0 = 1;\n}'));

it('switch with fallthrough-normal-normal-fallthrough cases', 
  generateTest('switch n { case 1..2: { a(); fallthrough; }, case 1..4: { b(); }, case 1..6: { c(); }, case 1..8: { d(); fallthrough; } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 4)) {\n    fallthrough0 = 2;\n    b();\n} else if (fallthrough0 < 2 && (n >= 1 && n <= 6)) {\n    fallthrough0 = 2;\n    c();\n} else if (fallthrough0 < 2 && (n >= 1 && n <= 8)) {\n    fallthrough0 = 2;\n    d();\n    fallthrough0 = 1;\n}'));

it('switch with normal-fallthrough-normal-fallthrough cases', 
  generateTest('switch n { case 1..2: { a(); }, case 1..4: { b(); fallthrough; }, case 1..6: { c(); }, case 1..8: { d(); fallthrough; } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n} else if (n >= 1 && n <= 4) {\n    fallthrough0 = 2;\n    b();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 6)) {\n    fallthrough0 = 2;\n    c();\n} else if (fallthrough0 < 2 && (n >= 1 && n <= 8)) {\n    fallthrough0 = 2;\n    d();\n    fallthrough0 = 1;\n}'));

it('switch with 2 fallthrough cases with default', 
  generateTest('switch n() { case a(): { a(); fallthrough; }, case 1..4: { b(); fallthrough; }, default: { def(); } }', 'let fallthrough0 = 0;\nlet switchStatement0 = n();\nif (fallthrough0 < 2 && (switchStatement0 >= 1 && switchStatement0 <= 4)) {\n    fallthrough0 = 2;\n    b();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2) {\n    def();\n}\nif (switchStatement0 === a()) {\n    fallthrough0 = 2;\n    a();\n    fallthrough0 = 1;\n}'));

it('switch with 3 fallthrough cases with default', 
  generateTest('switch n { case 1..2: { a(); fallthrough; }, case 1..4: { b(); fallthrough; }, case 1..6: { c(); fallthrough; }, default: { def(); } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 4)) {\n    fallthrough0 = 2;\n    b();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 6)) {\n    fallthrough0 = 2;\n    c();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2) {\n    def();\n}'));

it('switch with fallthrough-normal-fallthrough cases with default', 
  generateTest('switch n { case 1..2: { a(); fallthrough; }, case 1..4: { b(); }, case 1..6: { c(); fallthrough; }, default: { def(); } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 4)) {\n    fallthrough0 = 2;\n    b();\n} else if (fallthrough0 < 2 && (n >= 1 && n <= 6)) {\n    fallthrough0 = 2;\n    c();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2) {\n    def();\n}'));

it('switch with fallthrough-normal-normal-fallthrough cases with default', 
  generateTest('switch n { case 1..2: { a(); fallthrough; }, case 1..4: { b(); }, case 1..6: { c(); }, case 1..8: { d(); fallthrough; }, default: { def(); } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 4)) {\n    fallthrough0 = 2;\n    b();\n} else if (fallthrough0 < 2 && (n >= 1 && n <= 6)) {\n    fallthrough0 = 2;\n    c();\n} else if (fallthrough0 < 2 && (n >= 1 && n <= 8)) {\n    fallthrough0 = 2;\n    d();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2) {\n    def();\n}'));

it('switch with normal-fallthrough-normal-fallthrough cases with default', 
  generateTest('switch n { case 1..2: { a(); }, case 1..4: { b(); fallthrough; }, case 1..6: { c(); }, case 1..8: { d(); fallthrough; }, default: { def(); } }', 'let fallthrough0 = 0;\nif (n >= 1 && n <= 2) {\n    fallthrough0 = 2;\n    a();\n} else if (n >= 1 && n <= 4) {\n    fallthrough0 = 2;\n    b();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2 && (n >= 1 && n <= 6)) {\n    fallthrough0 = 2;\n    c();\n} else if (fallthrough0 < 2 && (n >= 1 && n <= 8)) {\n    fallthrough0 = 2;\n    d();\n    fallthrough0 = 1;\n}\nif (fallthrough0 < 2) {\n    def();\n}'));

});

describe('fat arrow:', function () {

it('fat arrow function expression',
  generateTest('var x = () => this.test;', 'let x = () => {\n    return this.test;\n};'));

it('fat arrow function expression inside another',
  generateTest('var x = () => () => this.test;', 'let x = () => {\n    return () => {\n        return this.test;\n    };\n};'));

});

describe('for-in and for-of with break and return:', function () {

it('for-in with break', 
  generateTest('for x in array { break; }',
    'for (let x of array) {\n    break;\n}'));

it('for-in with conditional break', 
  generateTest('for x in array { if x == 5 { break; } }',
    'for (let x of array) {\n    if (x === 5) {\n        break;\n    }\n}'));

it('for-in with return', 
  generateTest('for x in array { return 5; }', 
    'for (let x of array) {\n    return 5;\n}'));

it('for-in with return w/o argument', 
  generateTest('for x in array { return; }', 
    'for (let x of array) {\n    return;\n}'));

it('for-in with conditional return', 
  generateTest('for x in array { if x == 5 { return 5; } }', 
    'for (let x of array) {\n    if (x === 5) {\n        return 5;\n    }\n}'));

it('for-of with break', 
  generateTest('for x of obj { break; }',
    'for (let x of Object.keys(obj)) {\n    break;\n}'));

it('for-of with conditional break', 
  generateTest('for x of obj { if x == 5 { break; } }',
    'for (let x of Object.keys(obj)) {\n    if (x === 5) {\n        break;\n    }\n}'));

it('for-of with return', 
  generateTest('for x of obj { return 5; }', 
    'for (let x of Object.keys(obj)) {\n    return 5;\n}'));

it('for-of with conditional return', 
  generateTest('for x of obj { if x == 5 { return 5; } }', 
    'for (let x of Object.keys(obj)) {\n    if (x === 5) {\n        return 5;\n    }\n}'));

});

describe('regular expression literals:', function () {

it('regular expression literal',
  generateTest('var re = /ab+c/g;', 'let re = /ab+c/g;'));

});

describe('import statement:', function () {

it('import with 1 identifier',
  generateTest('import foo from "foobar";',
    'import { foo } from \"foobar\";'));

it('import with 2 identifiers',
  generateTest('import foo, bar from "foobar";',
    'import {\n    foo,\n    bar\n} from \"foobar\";'));

it('import with 1 identifier with as',
  generateTest('import foo as a from "foobar";',
    'import { foo as a } from \"foobar\";'));

it('import with 2 identifiers with as',
  generateTest('import foo as a, bar as b from "foobar";',
    'import {\n    foo as a,\n    bar as b\n} from \"foobar\";'));

it('batch import',
  generateTest('import * as lib from "lib";',
    'import * as lib from \"lib\";'));

it('import default',
  generateTest('import "jquery" as $;',
    'import $ from "jquery";'));

});

describe('export statement:', function () {

it('export with 1 identifier',
  generateTest('export foo;',
    'export {\n    foo\n};'));

it('export with 2 identifiers',
  generateTest('export foo, bar;',
    'export {\n    foo,\n    bar\n};'));

it('export with 1 identifier with as',
  generateTest('export foo as f;',
    'export {\n    foo as f\n};'));

it('export with 2 identifiers with as',
  generateTest('export foo as f, bar as b;',
    'export {\n    foo as f,\n    bar as b\n};'));

it('export with 1 identifier with source',
  generateTest('export foo from "foo";',
    'export {\n    foo\n} from \"foo\";'));

it('export with 2 identifiers with source',
  generateTest('export foo, bar from "foobar";',
    'export {\n    foo,\n    bar\n} from \"foobar\";'));      

it('batch export',
  generateTest('export * from "foobar";',
    'export * from \"foobar\";'));

it('export variable statement',
  generateTest('export var sqrt = Math.sqrt;',
    'export let sqrt = Math.sqrt;'));

it('export function statement',
  generateTest('export fn x() {}',
    'export function x() {\n}'));

it('export function statement with extends',
  generateTest('export fn x() extends A {}',
    'export function x() {\n    A.call(this);\n}\nx.prototype = Object.create(A);'));

it('export default identifier',
  generateTest('export default x;',
    'export default x;'));

it('export default function expression',
  generateTest('export default () -> {};',
    'export default function () {\n};'));

});

describe('curried functions:', function () {

it('single function curry',
  generateTest('var x = a^(1);',
    'let x = function () {\n    return a.apply(this, [1].concat([].slice.apply(arguments)));\n};'));

it('double function curry',
  generateTest('var x = a^(1)^(2, 3);',
    'let x = function () {\n    return function () {\n        return a.apply(this, [1].concat([].slice.apply(arguments)));\n    }.apply(this, [\n        2,\n        3\n    ].concat([].slice.apply(arguments)));\n};'));

it('single function curry with member expression',
  generateTest('var x = m.a^(1);',
    'let x = function () {\n    return m.a.apply(this, [1].concat([].slice.apply(arguments)));\n};'));

it('double function curry with member expression',
  generateTest('var x = m.a^(1)^(2, 3);',
    'let x = function () {\n    return function () {\n        return m.a.apply(this, [1].concat([].slice.apply(arguments)));\n    }.apply(this, [\n        2,\n        3\n    ].concat([].slice.apply(arguments)));\n};'));

it('curry function with splats', 
  generateErrorTest('var x = ::a^(::s...);', 
    [{ type: "InvalidFunctionCurrying" }]));

it('curry function with null propagating operator', 
  generateErrorTest('var x = ::m?.a^();', 
    [{ type: "InvalidFunctionCurrying" }]));

});

describe('destructuring assignment:', function () {

it('array pattern destructuring assignment', 
  generateTest('var [m, d, y] = [3, 14, 1977];', 
    'let [\n    m,\n    d,\n    y\n] = [\n    3,\n    14,\n    1977\n];'));

it('array pattern destructuring assignment with 1 null', 
  generateTest('var [, d, y] = [3, 14, 1977];', 
    'let [\n    ,\n    d,\n    y\n] = [\n    3,\n    14,\n    1977\n];'));

it('array pattern destructuring assignment with 2 nulls', 
  generateTest('var [,, y] = [3, 14, 1977];', 
    'let [\n    ,\n    ,\n    y\n] = [\n    3,\n    14,\n    1977\n];'));

it('array pattern destructuring assignment with sub array pattern', 
  generateTest('var [,, [a,b,c]] = [3, 14, [1,2,3]];', 
    'let [\n    ,\n    ,\n    [\n        a,\n        b,\n        c\n    ]\n] = [\n    3,\n    14,\n    [\n        1,\n        2,\n        3\n    ]\n];')); 

it('object pattern destructuring assignment', 
  generateTest('var { x: x } = f;', 
    'let {x: x} = f;'));

it('object pattern destructuring assignment with 2 properties', 
  generateTest('var { x: x, y: test } = f;', 
    'let {\n    x: x,\n    y: test\n} = f;'));

it('object pattern destructuring assignment with sub object pattern', 
  generateTest('var { x: x, y: { b: b } } = f;', 
    'let {\n    x: x,\n    y: {b: b}\n} = f;'));

it('swap', 
  generateTest('[x,y]=[y,x];', 
    '[\n    x,\n    y\n] = [\n    y,\n    x\n];'));

});

describe('object initializer shorthand:', function () {

it('object literal initializer shorthand', 
  generateTest('var a = {x, y};', 
    'let a = {\n    x,\n    y\n};'));

it('object pattern initializer shorthand', 
  generateTest('var {x, y} = {x: 1, y: 2};', 
    'let {x, y} = {\n    x: 1,\n    y: 2\n};'));

});

describe('property method assignments:', function () {

it('property method assignment', 
  generateTest('var object = {value: 42, toString() { return this.value; }};', 
    'let object = {\n    value: 42,\n    toString() {\n        return this.value;\n    }\n};'));

});

describe('pattern matching:', function () {

it('pattern match',
  generateTest('switch [a,b] { case [,5]: {} case [5,,,6]: {}}',
    'let switchStatement0 = [\n    a,\n    b\n];\nif (switchStatement0.length >= 2 && switchStatement0[1] === 5) {\n} else if (switchStatement0.length >= 4 && switchStatement0[0] === 5 && switchStatement0[3] === 6) {\n}'));

});

describe('delete statement:', function () {

it('delete statement',
  generateTest('delete x;', 'delete x;'));

});

describe('do-while loop:', function () {

it('do-while loop',
  generateTest('do { f(); } while true;', 'do {\n    f();\n} while (true);'));

});

describe('channels:', function () {

it('get expression',
  generateTest('f(<-x);', 
    'f(await x.get());'));

it('push statement',
  generateTest('a <- b;', 
    'a.push(b);'));

it('go statement',
  generateTest('go { f(); };', 
    '(async function () {\n    f();\n})();'));

it('get operator in global context',
  generateErrorTest('::f(<-::x);', 
    [{ type: "GetExpressionRequiresAsync" }]));

it('get operator in a normal function',
  generateErrorTest('fn h() { ::f(<-::x); }', 
    [{ type: "GetExpressionRequiresAsync" }]));     

it('get operator in an async function',
  generateErrorTest('async fn h() { ::f(<-::x); }', []));

it('get operator in an go statement',
  generateErrorTest('go { ::f(<-::x); };', []));

});

describe('undefined literals:', function () {

it('undefined literal', 
  generateTest('undefined;', 'void 0;'));

});