import { BindingEngine, valueConverter, IBindingContext, createBindingContext } from 'mframejs';


/*
 * Helper for tests
 **/
const e = (t: any) => {
  return BindingEngine.tokenizeParseAndTraverseAST(t.expressions, t.ctx);
};



/*
 * Context for our expression tests
 **/
const ctx: IBindingContext = createBindingContext({
  'a': 1,
  'b': 2,
  'c': 3,
  'd': 4,
  'e': 5,
  't1': 5,
  't2': 5,
  'a1': 5,
  'a2': 5,
  'over': 'no',
  '_': '_',
  $five: 5,
  $four: 4,
  item: {
    id: 1,
    name: 'name',
    lastName: 'lastname',
    age: 10,
    num1: 10,
    num2: 10,
    name1: 'cool',
    name2: 'wee'
  },
  internal2: {
    test: function () {
      return 5;
    },
    internal: {
      test: function () {
        return 5;
      }
    }
  },
  arrayTest: [],
  internal: {
    test: function () {
      return 5;
    },
    internal: {
      test: function () {
        return 5;
      }
    }
  },
  undefinedVal: undefined,
  nullVal: null,
  functionAdd2To: (a: number) => {
    return a + 2;
  },
  functionAddNumbers: (...a: any[]) => {
    return a.reduce((newval, current) => newval + current);
  },
  returnValueString: (a: number) => {
    return 'nooo:' + a; // I should never be called
  }
}, createBindingContext({
  'over': 'yes',
  returnValueString: (a: number) => {
    return a + '';
  },
  internal2: {
    test: function () {
      return 7;
    },
    internal: {
      test: function () {
        return 7;
      }
    }
  }
})
);



/*
 * Helper for tests
 * Error message gets kinda broken this way.
 * But test text is better, and easy to make many
 **/
const testExpr = (expression: string, tobe: any, ctx: IBindingContext, callable: Function) => {
  const description = `${expression} toBe(${tobe}<${typeof tobe}>)`;
  const result = e({ expressions: `${expression}`, ctx: ctx });
  callable(
    description,
    result,
    tobe);
};



@valueConverter('times2or')
export class ValueConverterTest1 {
  public toView(arg1: any, val: any) {
    val = val ? val : 2;

    return arg1 * val;
  }
}

@valueConverter('repeat')
export class ValueConverterTest2 {
  public toView(arg1: any) {
    return arg1 + ' ' + arg1;
  }
}



describe('tokenize parse nd traverse AST with expressions', () => {


  testExpr(
    'internal.test()',
    5, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'internal.internal.test()',
    5, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'internal2.test()',
    7, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'internal2.internal.test()',
    7, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '(2 === 2) && (3 === 3)',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '($five === $five) && ($four=== $four)',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    ' ( 2 = = = 2 ) & & ( 3 = = = 3 ) ',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${(2 === 2) && (3 === 3)}',
    'true', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '@{(2 === 2) && (3 === 3)}',
    'true', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'e*((a*(b+c))+d)',
    45, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '  e  * (    (  a  *  (b+  c))  +d)',
    45, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${e*((a*(b+c))+d)}',
    '45', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{e*((a*(b+c))+d)}',
    '45', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '4+-1',
    3, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '   4   +    -  1   ',
    3, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '   4+    -1   ',
    3, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${4+-1}',
    '3', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{4+-1}',
    '3', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${ 4   +  -   1     }',
    '3', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{ 4   +  -   1     }',
    '3', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '4+-1 + 4+-1',
    6, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '   4   +   - 1  +  4  +  -  1  ',
    6, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    `   4
   +   - 1
   +  4  +
     -  1  `,
    6, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${4+-1 + 4+-1}',
    '6', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{4+-1 + 4+-1}',
    '6', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${4+-1} + ${4+-1}',
    '3 + 3', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{4+-1} + @{4+-1}',
    '3 + 3', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${4+-1} + @{4+-1}',
    '3 + 3', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{4+-1} + ${4+-1}',
    '3 + 3', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '(2 === 1) && (3 === 3)',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'over',
    'yes', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'over | repeat',
    'yes yes', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'over | repeat | repeat',
    'yes yes yes yes', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'returnValueString(5)',
    '5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'Idonotexist()', // I should fail---
    undefined, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '(2 === 2) && !(3 === 3)',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '!(2 === 2) && (3 === 3)',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '!(2 === 2) && !(3 === 3)',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '(2 === 1) || (3 === 3)',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${(2 === 1) || (3 === 3)}',
    'true', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{(2 === 1) || (3 === 3)}',
    'true', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '-4 -1',
    -5, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    ' - 4 -1 ',
    -5, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '(a === a) && (c === d)',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '3 - (-2)',
    5, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'undefinedVal',
    undefined, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '!undefinedVal',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'undefinedVal === undefined',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    `undefinedVal ===
          undefined`,
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'undefinedVal !== undefined',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'notInContext',
    undefined, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '${notInContext}',
    '', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${undefined}',
    '', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    });


  testExpr(
    '${null}',
    '', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    });



  testExpr(
    '1 ? 1:2',
    1, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '0 ? 1:2',
    2, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'null ? 1:2',
    2, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'undefined ? 1:2',
    2, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '"" ? 1:2',
    2, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    "'' ? 1:2",
    2, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '`` ? 1 : 2',
    2, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '"x" ? 1:2',
    1, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    "'x' ? 1:2",
    1, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '`x` ? 1 : 2',
    1, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '1 ? true : false',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '1 ? "true" : "false"',
    'true', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'true ? true : false',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'true ? "true" : "false"',
    'true', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '!true ? true : false',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '!true ? "true" : "false"',
    'false', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '(!true) ? true : false',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '(!true) ? "true" : "false"',
    'false', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  // wrong value really...
  testExpr(
    '3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3',
    0.3333333333333335, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '5 | times2or',
    10, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${5 | times2or}',
    '10', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{5 | times2or}',
    '10', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '5 | times2or | times2or',
    20, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${5 | times2or | times2or}',
    '20', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '@{5 | times2or | times2or}',
    '20', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '5 | times2or:4',
    20, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '5 | times2or:4 | times2or',
    40, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '5 | repeat ',
    '5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr( // to see it does not mess up with result
    '5 | repeat & beghaviorthatdoesnotexist',
    '5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

  testExpr( // to see it does not mess up with result
    '5 | repeat & beghaviorthatdoesnotexist:555',
    '5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr( // to see it does not mess up with result
    '5 | repeat & beghaviorthatdoesnotexist:555 & beghaviorthatdoesnotexist:555',
    '5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '5 | repeat | repeat',
    '5 5 5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr( // to see it does not mess up with result
    '5 | repeat | repeat & beghaviorthatdoesnotexist',
    '5 5 5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr( // to see it does not mess up with result
    '5 | repeat | repeat & something:555',
    '5 5 5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr( // to see it does not mess up with result
    '5 | repeat | repeat & signal:555  & trigger:555',
    '5 5 5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '${5 | repeat | repeat}',
    '5 5 5 5', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'expr1:${5}, expr2:${5+5}, expr3:${9*2}, expr4:${5*5},',
    'expr1:5, expr2:10, expr3:18, expr4:25,', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '  ${"a"}${"Test"}  ',
    '  aTest  ', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '  ${"a"}${"Test"} ',
    '  aTest ', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'functionAdd2To(2)',
    4, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'functionAdd2To("2")',
    '22', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'functionAdd2To(item.age)',
    12, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'functionAdd2To(item.age.doesNotExist)',
    NaN, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'item.age.doesNotExist === undefined',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    "{'cool':a, a:b}",
    JSON.stringify({ 'cool': 1, 'a': 2 }), ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(result)).toBe(shouldbe);
      });
    }
  );


  testExpr(
    "{'cool':a}",
    JSON.stringify({ 'cool': 1 }), ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(result)).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'a1 = 2',
    2, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.a1).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'a2 = 1',
    1, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.a2).toBe(shouldbe);
      });
    }
  );

  testExpr(
    't1 += 1',
    6, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.t1).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'unknown += 1',
    NaN, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.unknown).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'somethingelse.item += 1',
    NaN, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.unknown).toBe(shouldbe);
      });
    }
  );

  testExpr(
    't2 -= 1',
    4, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.t2).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'item.num1 += 1',
    11, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.item.num1).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'item.num2-= 1',
    9, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.item.num2).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'item.name1 = 5',
    5, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.item.name1).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'item.somethingUnknown = 5',
    5, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.item.somethingUnknown).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'item.name2 = "name"',
    'name', ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.item.name2).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'shi.testing = 5',
    5, ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx.$context.shi.testing).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'shi.cool = {a:1}',
    JSON.stringify({ 'a': 1 }), ctx,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(ctx.$context.shi.cool)).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '{a:a, b:b}',
    JSON.stringify({ 'a': 1, 'b': 2 }), ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(result)).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '${a ? ${true ? ${true ? "false man": "yikes"}: "false man"} :false }',
    'false man', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '@{a ? @{true ? @{true ? "false man": "yikes"}: "false man"} :false }',
    'false man', ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'item["id"]',
    1, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

  testExpr(
    '5 + functionAddNumbers(1,1,1,1,1)',
    10, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '5 < 4',
    false, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '5 > 4',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '5 % 4',
    1, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '6 % 4',
    2, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '5 >= 5',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    '5 <= 5',
    true, ctx,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'value',
    'last',
    createBindingContext({ value: 'first' },
      createBindingContext({ value: 'middle' }, createBindingContext({ value: 'last' }))),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    '$value',
    'first',
    createBindingContext({ $value: 'first' },
      createBindingContext({ $value: 'middle' }, createBindingContext({ $value: 'last' }))),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj',
    JSON.stringify({
      name: 'string',
      value: 1
    }),
    createBindingContext({
      obj: {
        name: 'string',
        value: 1
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(result)).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'obj ? obj: 1',
    JSON.stringify({
      name: 'string',
      value: 1
    }),
    createBindingContext({
      obj: {
        name: 'string',
        value: 1
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(result)).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values[1]',
    3,
    createBindingContext({
      obj: {
        name: 'string',
        value: 1,
        values: [5, 3, 2]
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values["1"]',
    3,
    createBindingContext({
      obj: {
        name: 'string',
        value: 1,
        values: [5, 3, 2]
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values[obj.value]',
    3,
    createBindingContext({
      obj: {
        name: 'string',
        value: 1,
        values: [5, 3, 2]
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.a.b + (obj.b.a)',
    3,
    createBindingContext({
      obj: {
        name: 'string',
        value: 1,
        values: [5, 3, 2],
        a: {
          b: 1
        },
        b: {
          a: 2
        }
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'obj.values[obj.values[obj.value]]',
    3,
    createBindingContext({
      obj: {
        name: 'string',
        value: 2,
        values: [5, 3, 1]
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );



  testExpr(
    'obj.values[obj.values[2]]',
    3,
    createBindingContext({
      obj: {
        name: 'string',
        value: 2,
        values: [5, 3, 1]
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'testfn(obj.value) + testfn(obj.value)',
    4,
    createBindingContext({
      obj: {
        name: 'string',
        value: 2,
        values: [5, 3, 1]
      },
      testfn: function (x: any) {
        return x;
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values[obj.values[2]] | times2or',
    6,
    createBindingContext({
      obj: {
        name: 'string',
        value: 2,
        values: [5, 3, 1]
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'obj.values[obj.values[2]] | times2or:obj.values[obj.values[2]] ',
    9,
    createBindingContext({
      obj: {
        name: 'string',
        value: 2,
        values: [5, 3, 1]
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values[obj.values[2]] | times2or:obj.values[obj.values[2]] | times2or:obj.values[obj.values[2]]',
    27,
    createBindingContext({
      obj: {
        name: 'string',
        value: 2,
        values: [5, 3, 1]
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'test2fn(obj.value, obj.value) + testfn(obj.values[0])',
    9,
    createBindingContext({
      obj: {
        name: 'string',
        value: 2,
        values: [5, 3, 1]
      },
      testfn: function (x: any) {
        return x;
      },
      test2fn: function (x: any, y: any) {
        return x + y;
      }
    }),
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

  const ctx1 = createBindingContext({
    obj: {
      name: 'string',
      value1: 2,
      values: [5, 3, 1, 'wow', { person: { name: 'wow' } }]
    },
    callme(x: any) {
      return x;
    }

  });
  testExpr(
    'obj.values[1] = 5',
    5,
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.obj.values[1]).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values[obj.value1] += 5',
    6,
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.obj.values[2]).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'cool.value.test.wow = 5',
    5,
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.cool.value.test.wow).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'cool.value.test.man = 7',
    7,
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.cool.value.test.man).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'cool.value.test.wow2 = callme(9)',
    9,
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.cool.value.test.wow2).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'cool.value.test.wow4 = callme(obj.value1)',
    2,
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.cool.value.test.wow4).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'cool.value.test.wow5 = callme(obj["value1"])',
    2,
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.cool.value.test.wow5).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'cool.value.test.wow6 = callme({wow:"cool"})',
    JSON.stringify({ wow: 'cool' }),
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(ctx1.$context.cool.value.test.wow6)).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'cool.value.test.wow7 = callme({wow:{name:"cool"}})',
    JSON.stringify({ wow: { name: 'cool' } }),
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(ctx1.$context.cool.value.test.wow7)).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'cool.value.test.wow8 = {wow:{name:"cool"}}',
    JSON.stringify({ wow: { name: 'cool' } }),
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(ctx1.$context.cool.value.test.wow8)).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'wow8 = {wow:{name:"cool"}}',
    JSON.stringify({ wow: { name: 'cool' } }),
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(ctx1.$context.wow8)).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values[3] = {wow:{name:"cool"}}',
    JSON.stringify({ wow: { name: 'cool' } }),
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(JSON.stringify(ctx1.$context.obj.values[3])).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values[4].person.name',
    'wow',
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.obj.values[4].person.name).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'obj.values[4].person.last = "cool"',
    'cool',
    ctx1,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx1.$context.obj.values[4].person.last).toBe(shouldbe);
      });
    }
  );

  const ctx10 = createBindingContext({
    obj: {
      number1: 2,
      number2: 2,
      number3: 5,
      number4: 5
    }
  });

  testExpr(
    'obj.number1 *= 2',
    4,
    ctx10,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx10.$context.obj.number1).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'obj.number2 /= 2',
    1,
    ctx10,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx10.$context.obj.number2).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'obj.number3 *= 2',
    10,
    ctx10,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx10.$context.obj.number3).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'obj.number4 /= 2',
    2.5,
    ctx10,
    (description: string, _result: any, shouldbe: any) => {
      it(description, () => {
        expect(ctx10.$context.obj.number4).toBe(shouldbe);
      });
    }
  );

  const ctx11 = createBindingContext({
    obj: {
      number1: 2,
      number2: 2,
      number3: 5,
      number4: 5
    },
    testArray1: [],
    testArray2: [],
    testArray3: [5],
    testArray4: [1, 2, 3]
  });

  testExpr(
    'testArray1.length',
    0,
    ctx11,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );


  testExpr(
    'testArray2.push(5,1)',
    2,
    ctx11,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'testArray3.pop()',
    5,
    ctx11,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

  testExpr(
    'testArray4.join("-")',
    '1-2-3',
    ctx11,
    (description: string, result: any, shouldbe: any) => {
      it(description, () => {
        expect(result).toBe(shouldbe);
      });
    }
  );

});


