describe('Ext.app.bind.Template', function () {
    var BindTemplate;

    function getNumFragments (tpl) {
        var count = 0;
        for (var i = tpl.buffer.length; i-- > 0; ) {
            if (tpl.buffer[i]) {
                ++count;
            }
        }
        return count;
    }

    function getNumSlots (tpl) {
        var count = 0;
        for (var i = tpl.slots.length; i-- > 0; ) {
            if (tpl.slots[i]) {
                ++count;
            }
        }
        return count;
    }

    beforeEach(function () {
        BindTemplate = Ext.app.bind.Template;
    });

    describe('tokens', function () {
        it('should parse on first use', function () {
            var tpl = new BindTemplate('Hello {foo}');

            expect(tpl.tokens).toBe(null);

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            expect(getNumFragments(tpl)).toBe(1);
            expect(getNumSlots(tpl)).toBe(1);
        });

        it('should parse simple names', function () {
            var tpl = new BindTemplate('Hello {foo} {bar}');

            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo', 'bar']);

            expect(getNumFragments(tpl)).toBe(2);
            expect(getNumSlots(tpl)).toBe(2);
        });

        it('should parse dotted names', function () {
            var tpl = new BindTemplate('Hello {foo.bar} {bar.foo}');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            expect(getNumFragments(tpl)).toBe(2);
            expect(getNumSlots(tpl)).toBe(2);
        });

        it('should consolidate tokens', function () {
            var tpl = new BindTemplate('Hello {foo.bar} {bar} {foo.bar} {bar}');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar']);

            expect(getNumFragments(tpl)).toBe(4);
            expect(getNumSlots(tpl)).toBe(4);
        });

        it('should match slots to consolidated tokens', function () {
            //                          1      2       3    4  5        6
            var tpl = new BindTemplate('Hello {foo.bar}{bar} - {foo.bar}{bar}');
            tpl.parse();

            expect(getNumFragments(tpl)).toBe(2);
            expect(getNumSlots(tpl)).toBe(4);

            expect(typeof tpl.slots[1]).toBe('function');
            expect(typeof tpl.slots[2]).toBe('function');
            // slots[3] is null due to " - " in buffer[3]
            expect(typeof tpl.slots[4]).toBe('function');
            expect(typeof tpl.slots[5]).toBe('function');
        });

        it("should not attempt to parse outside of curly braces", function() {
            var tpl = new BindTemplate('Hello `{foo}`!'),
                tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([5]);
            expect(s).toBe('Hello `5`!');

        });
    });

    describe('unary operators', function(){
        it('should parse -', function(){
            var tpl = new BindTemplate('Hello {foo.bar + -5}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar']);

            var s = tpl.apply([10]);
            expect(s).toBe('Hello 5!');
        });

        it('should parse - before an expression', function(){
            var tpl = new BindTemplate('Hello {foo.bar + -(bar + 3):number("0.00")}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar']);

            var s = tpl.apply([10, 7]);
            expect(s).toBe('Hello 0!'); // 10 - '10.00'
        });

        it('should parse - before an expression and follow parans', function(){
            var tpl = new BindTemplate('Hello {(foo.bar + -(bar + 3)):number("0.00")}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar']);

            var s = tpl.apply([10, 7]);
            expect(s).toBe('Hello 0.00!');
        });

        it('should parse - before parans and before literal', function(){
            var tpl = new BindTemplate('Hello {(foo.bar + -(bar +- 3)):number("0.00")}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar']);

            var s = tpl.apply([10, 7]);
            expect(s).toBe('Hello 6.00!');
        });

        it('should parse - before parans and before token', function(){
            var tpl = new BindTemplate('Hello {(foo.bar + -(bar -- foo)):number("0.00")}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar', 'foo']);

            var s = tpl.apply([10, 7, 4]);
            expect(s).toBe('Hello -1.00!');
        });

        it('should parse @ unary operator', function(){
            var tpl = new BindTemplate('Hello {@Ext.justTemp}!');

            Ext.justTemp = 'foo';
            var s = tpl.apply();
            expect(s).toBe('Hello foo!');
            Ext.justTemp = 'bar';
            s = tpl.apply();
            expect(s).toBe('Hello bar!');
            expect(tpl.isStatic()).toBe(false);
            Ext.justTemp = null;
        });

    });

    describe('binary operators', function(){
        it('should parse + operations', function(){
            var tpl = new BindTemplate('Hello {foo.bar + bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([5, 7]);
            expect(s).toBe('Hello 12!');
        });

        it('should parse - operations', function(){
            var tpl = new BindTemplate('Hello {foo.bar - bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([5, 7]);
            expect(s).toBe('Hello -2!');
        });

        it('should parse * operations', function(){
            var tpl = new BindTemplate('Hello {foo.bar * bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([5, 7]);
            expect(s).toBe('Hello 35!');
        });

        it('should parse / operations', function(){
            var tpl = new BindTemplate('Hello {foo.bar / bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([10, 2]);
            expect(s).toBe('Hello 5!');
        });

        it('should parse > operations', function(){
            var tpl = new BindTemplate('{foo.bar > bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([10, 2]);
            expect(s).toBe('true!');
        });

        it('should parse < operations', function(){
            var tpl = new BindTemplate('{foo.bar < bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([2, 10]);
            expect(s).toBe('true!');
        });

        it('should parse >= operations', function(){
            var tpl = new BindTemplate('{foo.bar >= bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([10, 10]);
            expect(s).toBe('true!');
        });

        it('should parse <= operations', function(){
            var tpl = new BindTemplate('{foo.bar <= bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([10, 10]);
            expect(s).toBe('true!');
        });

        it('should parse === operations', function(){
            var tpl = new BindTemplate('{foo.bar === bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([10, '10']);
            expect(s).toBe('false!');
        });

        it('should parse == operations', function(){
            var tpl = new BindTemplate('{foo.bar == bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([10, '10']);
            expect(s).toBe('true!');
        });

        it('should parse !== operations', function(){
            var tpl = new BindTemplate('{foo.bar !== bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([10, '10']);
            expect(s).toBe('true!');
        });

        it('should parse != operations', function(){
            var tpl = new BindTemplate('{foo.bar != bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo']);

            var s = tpl.apply([10, '10']);
            expect(s).toBe('false!');
        });

        it('should parse && operations', function(){
            var tpl = new BindTemplate('{foo.bar > bar.foo && bar > 5}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo', 'bar']);

            var s = tpl.apply([10, 5, 3]);
            expect(s).toBe('false!');
        });

        it('should parse || operations', function(){
            var tpl = new BindTemplate('{foo.bar > bar.foo || bar > 5}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar.foo', 'bar']);

            var s = tpl.apply([10, 5, 3]);
            expect(s).toBe('true!');
        });

        it('should parse operations by priority', function(){
            var tpl = new BindTemplate('Hello {foo.bar * foo + bar / bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'foo', 'bar', 'bar.foo']);

            var s = tpl.apply([10, 2, 5, 2]);
            expect(s).toBe('Hello 22.5!');
        });
    });

    describe('ternary operator', function(){
        it('should parse token condition', function () {
            var tpl = new BindTemplate('Hello {foo ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([true]);
            expect(s).toBe('Hello 5');
        });

        it('should parse binary condition >', function () {
            var tpl = new BindTemplate('Hello {foo > 3 ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([2]);
            expect(s).toBe('Hello 6');
        });

        it('should parse binary condition >=', function () {
            var tpl = new BindTemplate('Hello {foo >= 3 ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([3]);
            expect(s).toBe('Hello 5');
        });

        it('should parse binary condition <', function () {
            var tpl = new BindTemplate('Hello {foo < 3 ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([2]);
            expect(s).toBe('Hello 5');
        });

        it('should parse binary condition <=', function () {
            var tpl = new BindTemplate('Hello {foo <= 3 ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([4]);
            expect(s).toBe('Hello 6');
        });

        it('should parse binary condition ==', function () {
            var tpl = new BindTemplate('Hello {foo == "3" ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([3]);
            expect(s).toBe('Hello 5');
        });

        it('should parse binary condition ===', function () {
            var tpl = new BindTemplate('Hello {foo === "3" ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([3]);
            expect(s).toBe('Hello 6');
        });

        it('should parse binary condition !=', function () {
            var tpl = new BindTemplate('Hello {foo != "3" ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([3]);
            expect(s).toBe('Hello 6');
        });

        it('should parse binary condition !==', function () {
            var tpl = new BindTemplate('Hello {foo !== "3" ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([3]);
            expect(s).toBe('Hello 5');
        });

        it('should parse condition with format fn', function () {
            var tpl = new BindTemplate('Hello {foo:this.fn ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([4], {
                fn: function(){ return false; }
            });
            expect(s).toBe('Hello 6');
        });

        it('should parse condition with format fn and args', function () {
            var tpl = new BindTemplate('Hello {foo:this.fn("testing", 4) ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([4], {
                fn: function(){ return false; }
            });
            expect(s).toBe('Hello 6');
        });

        it('should parse condition with chained format fn and args', function () {
            var tpl = new BindTemplate('Hello {foo:this.fn("testing", 4):this.fn2 ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([4], {
                fn: function(){ return false; },
                fn2: function(){ return true; }
            });
            expect(s).toBe('Hello 5');
        });

        it('should parse condition with chained and nested format fn and args', function () {
            var tpl = new BindTemplate('Hello {foo:this.fn("testing", bar:this.fn3(null, true)):this.fn2 ? 5 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo', 'bar']);

            var s = tpl.apply([4], {
                fn: function(){ return false; },
                fn2: function(){ return true; },
                fn3: function(){ return 5; }
            });
            expect(s).toBe('Hello 5');
        });

        it('should parse true part with literal', function () {
            var tpl = new BindTemplate('Hello {foo ? "test" : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([true]);
            expect(s).toBe('Hello test');
        });

        it('should parse true part with number', function () {
            var tpl = new BindTemplate('Hello {foo ? .04 : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([true]);
            expect(s).toBe('Hello 0.04');
        });

        it('should parse true part with null', function () {
            var tpl = new BindTemplate('Hello {foo ? null : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([true]);
            expect(s).toBe('Hello ');
        });

        it('should parse true part with boolean', function () {
            var tpl = new BindTemplate('Hello {foo ? true : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([true]);
            expect(s).toBe('Hello true');
        });

        it('should parse true part enclosed in parans with simple format fn', function () {
            var tpl = new BindTemplate('Hello {foo ? (bar:number) : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo', 'bar']);

            var s = tpl.apply([true, 5]);
            expect(s).toBe('Hello 5');
        });

        it('should parse true part enclosed in parans with format fn and args', function () {
            var tpl = new BindTemplate('Hello {foo ? (bar:number("0.00")) : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo', 'bar']);

            var s = tpl.apply([true, 5]);
            expect(s).toBe('Hello 5.00');
        });

        it('should parse true part with basic algebra inside parans', function () {
            var tpl = new BindTemplate('Hello {foo ? (bar + 5 * foo.bar) : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo', 'bar', 'foo.bar']);

            var s = tpl.apply([true, 4, 3]);
            expect(s).toBe('Hello 19');
        });

        it('should parse true part with basic algebra and no parans', function () {
            var tpl = new BindTemplate('Hello {foo ? bar + 5 * foo.bar : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo', 'bar', 'foo.bar']);

            var s = tpl.apply([true, 4, 3]);
            expect(s).toBe('Hello 19');
        });

        it('should parse true part with basic algebra and format fn', function () {
            var tpl = new BindTemplate('Hello {foo ? ( ( bar + 5 * foo.bar:this.fn( 2 ) / 4 ):round:number("0.00") ) : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo', 'bar', 'foo.bar']);

            var s = tpl.apply([true, 4, 3], {
                fn: function(v, factor){
                    return v * factor;
                }
            });
            expect(s).toBe('Hello 12.00');
        });

        it('should parse true part with nested ternary', function () {
            var tpl = new BindTemplate('Hello {foo ? (bar ? (foo.bar + 9) : "failed") : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo', 'bar', 'foo.bar']);

            var s = tpl.apply([true, 4, 3]);
            expect(s).toBe('Hello 12');
        });

        it('should parse true part with nested ternary and no parans', function () {
            var tpl = new BindTemplate('Hello {foo ? bar ? foo.bar + 9 : "failed" : 6}');

            var tokens = tpl.getTokens();
            expect(tokens).toEqual(['foo', 'bar', 'foo.bar']);

            var s = tpl.apply([true, 4, 3]);
            expect(s).toBe('Hello 12');
        });

    });

    describe('combined unary and binary operators', function(){
        it('should parse binary and unary -', function(){
            var tpl = new BindTemplate('Hello {foo.bar --5}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar']);

            var s = tpl.apply([10]);
            expect(s).toBe('Hello 15!');
        });

        it('should parse binary + and unary -', function(){
            var tpl = new BindTemplate('Hello {foo.bar +-5}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar']);

            var s = tpl.apply([10]);
            expect(s).toBe('Hello 5!');
        });

        it('should parse binary + and unary ! and -', function(){
            var tpl = new BindTemplate('Hello {foo.bar + !-5}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar']);

            var s = tpl.apply([10]);
            expect(s).toBe('Hello 10!'); // 10 + false
        });

        it('should parse ! operator in front of open paran', function(){
            var tpl = new BindTemplate('Hello {foo.bar + !(bar:number("0.00"))}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar']);

            var s = tpl.apply([10, 4]);
            expect(s).toBe('Hello 10!'); // 10 + false
        });

        it('should parse ! operator in front of a token', function(){
            var tpl = new BindTemplate('Hello {foo.bar + !bar}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'bar']);

            var s = tpl.apply([10, false]);
            expect(s).toBe('Hello 11!'); // 10 + true
        });

        it('should parse ! operator in front of a @', function(){
            var tpl = new BindTemplate('Hello {foo.bar + !@Ext.versions.core}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar']);

            var s = tpl.apply([10]);
            expect(s).toBe('Hello 10!'); // 10 + false
        });

    });

    describe('algebra', function(){

        it('should parse basic algebra', function(){
            var tpl = new BindTemplate('{foo:round + 2}');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo']);

            var s = tpl.apply([15.6]);
            expect(s).toBe(18);
        });

        it('should parse operations by priority', function(){
            var tpl = new BindTemplate('Hello {(foo.bar * foo + bar +-test ? 7 : 1):this.thing(3) / bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'foo', 'bar', 'test', 'bar.foo']);

            var s = tpl.apply([10, 2, 5, 25, 2], {
                thing: function(v, factor){
                    return v * factor;
                }
            });
            expect(s).toBe('Hello 1.5!');
        });

        it('should parse operations and apply formulas', function(){
            var tpl = new BindTemplate('Hello {(foo.bar * foo + bar):this.thing(3) / bar.foo}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'foo', 'bar', 'bar.foo']);

            var s = tpl.apply([10, 2, 5, 2], {
                thing: function(v, factor){
                    return v * factor;
                }
            });
            expect(s).toBe('Hello 37.5!');
        });

        it('should parse operations in formula arguments', function(){
            var tpl = new BindTemplate('Hello {((foo.bar * foo + bar):this.thing(bar + test:this.thing(3)) / bar.foo):number("0.00")}!');
            var tokens = tpl.getTokens();

            expect(tokens).toEqual(['foo.bar', 'foo', 'bar', 'test', 'bar.foo']);

            var s = tpl.apply([10, 2, 5, 7, 25], {
                thing: function(v, factor){
                    return v * factor;
                }
            });
            expect(s).toBe('Hello 26.00!');
        });

        it('should parse complex operations', function(){
            var tpl = new BindTemplate('Hello {(foo.bar + bar.foo:this.thing):number("0.00")}!');

            var s = tpl.apply([5, 7], {
                thing: function(v){
                    return v * 2;
                }
            });
            expect(s).toBe('Hello 19.00!');
        });


    });

    describe('default formatters', function () {
        it('should parse', function () {
            var tpl = new BindTemplate('Hello {foo:number} {bar.foo:lowercase}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar.foo']);

            var s = tpl.apply([5, 'SENCHA']);
            expect(s).toBe('Hello 5 sencha');
        });

        it('should parse chained formatters', function(){
            var tpl = new BindTemplate('Hello {foo:lowercase:capitalize} {bar.foo:number("0.00")}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar.foo']);

            var s = tpl.apply(['SENCHA', 23]);
            expect(s).toBe('Hello Sencha 23.00');
        });

        it('should parse nested formatters', function(){
            var tpl = new BindTemplate('Hello {foo:format(bar:pick("First: \\"param\\"", foo.bar:number("0")))}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar', 'foo.bar']);

            var s = tpl.apply(['Result: {0}', false, 5]);
            expect(s).toBe('Hello Result: First: "param"');
        });

        it('should parse complex nested formatters', function(){
            var tpl = new BindTemplate('Hello {foo:format(bar:capitalize:leftPad(5, "x"))}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar']);

            var s = tpl.apply(['there, {0}', 'john']);
            expect(s).toBe('Hello there, xJohn');
        });

        it('should parse nested and chained formatters', function(){
            var tpl = new BindTemplate('Hello {foo.bar:leftPad(!foo.test:pick(bar,foo), "X"):uppercase:ellipsis(8)} there and {foo:capitalize}!');
            expect(tpl.getTokens()).toEqual(['foo.bar', 'foo.test', 'bar', 'foo']);

            var s = tpl.apply(['sencha', true, 10, 'sencha']);
            expect(s).toBe('Hello XXXXS... there and Sencha!');
        });

        it('should parse escaped strings', function(){
            var tpl = new BindTemplate("{foo:leftPad(13, 'You\\'re ok ')}");
            // this expressions will fail: {foo:leftPad("You\", hi!",2)} or {foo:leftPad("(You\")",2)}
            expect(tpl.getTokens()).toEqual(['foo']);

            var s = tpl.apply(['now']);
            expect(s).toBe('You\'re ok now');
        });

        it('should parse more escaped strings', function(){
            var tpl = new BindTemplate('{foo:leftPad(10, "Y\\"): ")}');
            // this expression will fail: {foo:date("Y\"",2)}
            expect(tpl.getTokens()).toEqual(['foo']);

            var s = tpl.apply(['hello']);
            expect(s).toBe('Y"): hello');
        });

        it('should parse arguments', function () {
            var tpl = new BindTemplate('Hello {foo:number("0.00")} {bar.foo:number("0,000.00")}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar.foo']);

            var s = tpl.apply([4554, 4554]);
            expect(s).toBe('Hello 4554.00 4,554.00');
        });

        it('should parse boolean arguments', function () {
            var tpl = new BindTemplate('Hello {foo:toggle("Flex", false)} {bar.foo:defaultValue(true)}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar.foo']);

            var s = tpl.apply(['Flex', undefined]);
            expect(s).toBe('Hello false true');
        });

        it('should parse arguments that are functions', function(){
            var tpl = new BindTemplate('Hello {foo:defaultValue(bar.foo:lowercase)}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar.foo']);

            var s = tpl.apply([undefined, 'THERE']);
            expect(s).toBe('Hello there');
        });

        it('should apply simple formatting', function () {
            var tpl = new BindTemplate('Hello {foo:number} {bar.foo:date("Y-m-d")} '+
                '-- {foo:number("0.00")}');

            var s = tpl.apply([123.456, new Date(2013, 2, 2)]);
            expect(s).toBe('Hello 123.456 2013-03-02 -- 123.46');
        });

        it('should apply complex formatting', function () {
            // The "," inside a string argument makes splitting on commas and producing an
            // args array early impossible (if we are to respect global references in them
            // as well)... but still needs to work.
            var tpl = new BindTemplate('Hello {foo:number} {bar.foo:date("Y-m-d")} '+
                '-- {foo:number("0,000.00")}');

            var s = tpl.apply([123456.789, new Date(2013, 2, 2)]);
            expect(s).toBe('Hello 123456.789 2013-03-02 -- 123,456.79');
        });
    });

    describe('scoped formatters', function () {
        it('should parse', function () {
            var tpl = new BindTemplate('Hello {foo:this.fn} {bar.foo:this.fn2}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar.foo']);

            var s = tpl.apply([5, 6], {
                fn: function(v){
                    return v + 1;
                },
                fn2: function(v){
                    return v * 2;
                }
            });
            expect(s).toBe('Hello 6 12');
        });

        it('should parse arguments', function () {
            var tpl = new BindTemplate('Hello {foo:this.fn(4)} {bar.foo:this.fn2(20)}');
            expect(tpl.getTokens()).toEqual(['foo', 'bar.foo']);

            var s = tpl.apply([5, 6], {
                fn: function(v, a){
                    return v + a;
                },
                fn2: function(v, a){
                    return v * a;
                }
            });
            expect(s).toBe('Hello 9 120');
        });

        it('should apply simple formatting', function () {
            var tpl = new BindTemplate('Hello {foo:number} {bar.foo:date("Y-m-d")} '+
                '-- {foo:this.number("0.00")}');

            var s = tpl.apply([123.456, new Date(2013, 2, 2)], {
                scale: 2,
                number: function (v, str) {
                    return '[[' + Ext.util.Format.number(v * this.scale, str) + ']]';
                }
            });

            expect(s).toBe('Hello 123.456 2013-03-02 -- [[246.91]]');
        });

        it('should apply complex formatting', function () {
            // This template uses a global reference as an argument. Odd but it works in
            // other templates.
            var tpl = new BindTemplate('Hello {foo:number} {bar.foo:date("Y-m-d")} '+
                '-- {foo:this.thing(@Ext.versions.core)}');

            var s = tpl.apply([123.456, new Date(2013, 2, 2)], {
                text: '::',
                thing: function (v, str) {
                    return this.text + v + '=' + str + this.text;
                }
            });

            expect(s).toBe('Hello 123.456 2013-03-02 -- ::123.456=' +
                Ext.getVersion('core') + '::');
        });

        it('should apply chained and nested formatting', function(){
            var tpl = new BindTemplate('Hello {!foo.bar:pick(bar:number, "test"):number(\'0,000.00\')}, this is a {foo.test:this.thing("test", !test:pick("\\"man{}\\"",\'(joe)\'))}!');

            var s = tpl.apply([true, 123.456, 'complex', true], {
                text: '::',
                thing: function(v, str, a){
                    return this.text + v + '=' + str + this.text + ' (' + a + ')';
                }
            });
            expect(s).toBe('Hello 123.46, this is a ::complex=test:: ("man{}")!');
        });
    });

    describe('syntax errors', function(){
        it('should fail when there\'s a format fn without prefixed token', function () {
            var tpl = new BindTemplate('Hello { :number }!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when @ prefixes an ! operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + @!Ext.versions.core}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when @ prefixes a number', function () {
            var tpl = new BindTemplate('Hello {foo.bar + @5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when @ prefixes a string', function () {
            var tpl = new BindTemplate('Hello {foo.bar + @"test"}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when @ prefixes other operators', function () {
            var tpl = new BindTemplate('Hello {foo.bar + @("test"}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when there\'s a missing paran', function () {
            var tpl = new BindTemplate('Hello {foo.bar + (foo:number}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when specifying an invalid Ext.util.Format fn', function () {
            var tpl = new BindTemplate('Hello {foo.bar + (foo:justTesting}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when there is an unexpected operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + ! $ (foo:number)}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when there is an unknown operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + dd[foo:number]}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on unexpected . token', function () {
            var tpl = new BindTemplate('Hello {foo.bar + dd.(foo:number)}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on not defined unary * operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + * 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary in format fn name', function () {
            var tpl = new BindTemplate('Hello {foo.bar:*number}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when starting with an unknown operator', function () {
            var tpl = new BindTemplate('Hello { % foo.bar:number }!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when using open curly inside expression', function () {
            var tpl = new BindTemplate('Hello { { foo.bar:number }!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when using close curly inside expression', function () {
            var tpl = new BindTemplate('Hello { foo.bar:}number }!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on wrong literals', function () {
            var tpl = new BindTemplate('Hello { foo.bar:this.test("yep\" it fails") }!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when ending with an operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar:number + }!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary / operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + / 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary * operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + * 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary && operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + && 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary || operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + || 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary > operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + > 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary >= operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + >= 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary < operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + < 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary <= operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + <= 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary == operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + == 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary === operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + === 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary != operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + != 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail on undefined unary !== operator', function () {
            var tpl = new BindTemplate('Hello {foo.bar + !== 5}!');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

        it('should fail when the compiled function fails on global objects', function () {
            var tpl = new BindTemplate('Hello {foo.bar + @Something.b}!');

            expect(tpl.getTokens()).toEqual(['foo.bar']);
            expect(function() {
                tpl.apply([3]);
            }).toThrow();

        });

        it('should fail when the compiled function fails on missing scope function', function () {
            var tpl = new BindTemplate('Hello {foo.bar:this.test}!');

            expect(tpl.getTokens()).toEqual(['foo.bar']);
            expect(function() {
                tpl.apply([3]);
            }).toThrow();

        });

        it('should fail when ambiguous ternary provided', function () {
            var tpl = new BindTemplate('Hello {foo ? bar:number : 6}');

            expect(function() {
                tpl.getTokens();
            }).toThrow();

        });

    });
});
