describe('blocks', function() {
  it('array', function() {
    var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
    var hash = {
      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
      world: 'world'
    };
    shouldCompileTo(
      string,
      hash,
      'goodbye! Goodbye! GOODBYE! cruel world!',
      'Arrays iterate over the contents when not empty'
    );

    shouldCompileTo(
      string,
      { goodbyes: [], world: 'world' },
      'cruel world!',
      'Arrays ignore the contents when empty'
    );
  });

  it('array without data', function() {
    var string =
      '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
    var hash = {
      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
      world: 'world'
    };
    shouldCompileTo(
      string,
      [hash, , , false],
      'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'
    );
  });

  it('array with @index', function() {
    var string =
      '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
    var hash = {
      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
      world: 'world'
    };

    var template = CompilerContext.compile(string);
    var result = template(hash);

    equal(
      result,
      '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
      'The @index variable is used'
    );
  });

  it('empty block', function() {
    var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
    var hash = {
      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
      world: 'world'
    };
    shouldCompileTo(
      string,
      hash,
      'cruel world!',
      'Arrays iterate over the contents when not empty'
    );

    shouldCompileTo(
      string,
      { goodbyes: [], world: 'world' },
      'cruel world!',
      'Arrays ignore the contents when empty'
    );
  });

  it('block with complex lookup', function() {
    var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
    var hash = {
      name: 'Alan',
      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
    };

    shouldCompileTo(
      string,
      hash,
      'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
      'Templates can access variables in contexts up the stack with relative path syntax'
    );
  });

  it('multiple blocks with complex lookup', function() {
    var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
    var hash = {
      name: 'Alan',
      goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
    };

    shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
  });

  it('block with complex lookup using nested context', function() {
    var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}';

    shouldThrow(function() {
      CompilerContext.compile(string);
    }, Error);
  });

  it('block with deep nested complex lookup', function() {
    var string =
      '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
    var hash = {
      omg: 'OMG!',
      outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
    };

    shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
  });

  it('works with cached blocks', function() {
    var template = CompilerContext.compile(
      '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}',
      { data: false }
    );

    var result = template({
      person: [
        { first: 'Alan', last: 'Johnson' },
        { first: 'Alan', last: 'Johnson' }
      ]
    });
    equals(result, 'Alan JohnsonAlan Johnson');
  });

  describe('inverted sections', function() {
    it('inverted sections with unset value', function() {
      var string =
        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
      var hash = {};
      shouldCompileTo(
        string,
        hash,
        'Right On!',
        "Inverted section rendered when value isn't set."
      );
    });

    it('inverted section with false value', function() {
      var string =
        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
      var hash = { goodbyes: false };
      shouldCompileTo(
        string,
        hash,
        'Right On!',
        'Inverted section rendered when value is false.'
      );
    });

    it('inverted section with empty set', function() {
      var string =
        '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
      var hash = { goodbyes: [] };
      shouldCompileTo(
        string,
        hash,
        'Right On!',
        'Inverted section rendered when value is empty set.'
      );
    });

    it('block inverted sections', function() {
      shouldCompileTo(
        '{{#people}}{{name}}{{^}}{{none}}{{/people}}',
        { none: 'No people' },
        'No people'
      );
    });
    it('chained inverted sections', function() {
      shouldCompileTo(
        '{{#people}}{{name}}{{else if none}}{{none}}{{/people}}',
        { none: 'No people' },
        'No people'
      );
      shouldCompileTo(
        '{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}',
        { none: 'No people' },
        'No people'
      );
      shouldCompileTo(
        '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}',
        { none: 'No people' },
        'No people'
      );
    });
    it('chained inverted sections with mismatch', function() {
      shouldThrow(function() {
        shouldCompileTo(
          '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}',
          { none: 'No people' },
          'No people'
        );
      }, Error);
    });

    it('block inverted sections with empty arrays', function() {
      shouldCompileTo(
        '{{#people}}{{name}}{{^}}{{none}}{{/people}}',
        { none: 'No people', people: [] },
        'No people'
      );
    });
  });

  describe('standalone sections', function() {
    it('block standalone else sections', function() {
      shouldCompileTo(
        '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
        { none: 'No people' },
        'No people\n'
      );
      shouldCompileTo(
        '{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n',
        { none: 'No people' },
        'No people\n'
      );
      shouldCompileTo(
        '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
        { none: 'No people' },
        'No people\n'
      );
    });
    it('block standalone else sections can be disabled', function() {
      shouldCompileTo(
        '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
        [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
        '\nNo people\n\n'
      );
      shouldCompileTo(
        '{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
        [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
        '\nNo people\n\n'
      );
    });
    it('block standalone chained else sections', function() {
      shouldCompileTo(
        '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n',
        { none: 'No people' },
        'No people\n'
      );
      shouldCompileTo(
        '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n',
        { none: 'No people' },
        'No people\n'
      );
    });
    it('should handle nesting', function() {
      shouldCompileTo(
        '{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.',
        { data: [1, 3, 5] },
        '1\n3\n5\nOK.'
      );
    });
  });

  describe('compat mode', function() {
    it('block with deep recursive lookup lookup', function() {
      var string =
        '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
      var hash = { omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };

      shouldCompileTo(
        string,
        [hash, undefined, undefined, true],
        'Goodbye cruel OMG!'
      );
    });

    it('block with deep recursive pathed lookup', function() {
      var string =
        '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
      var hash = {
        omg: { yes: 'OMG!' },
        outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
      };

      shouldCompileTo(
        string,
        [hash, undefined, undefined, true],
        'Goodbye cruel OMG!'
      );
    });
    it('block with missed recursive lookup', function() {
      var string =
        '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
      var hash = {
        omg: { no: 'OMG!' },
        outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
      };

      shouldCompileTo(
        string,
        [hash, undefined, undefined, true],
        'Goodbye cruel '
      );
    });
  });

  describe('decorators', function() {
    it('should apply mustache decorators', function() {
      var helpers = {
        helper: function(options) {
          return options.fn.run;
        }
      };
      var decorators = {
        decorator: function(fn) {
          fn.run = 'success';
          return fn;
        }
      };
      shouldCompileTo(
        '{{#helper}}{{*decorator}}{{/helper}}',
        { hash: {}, helpers: helpers, decorators: decorators },
        'success'
      );
    });
    it('should apply allow undefined return', function() {
      var helpers = {
        helper: function(options) {
          return options.fn() + options.fn.run;
        }
      };
      var decorators = {
        decorator: function(fn) {
          fn.run = 'cess';
        }
      };
      shouldCompileTo(
        '{{#helper}}{{*decorator}}suc{{/helper}}',
        { hash: {}, helpers: helpers, decorators: decorators },
        'success'
      );
    });

    it('should apply block decorators', function() {
      var helpers = {
        helper: function(options) {
          return options.fn.run;
        }
      };
      var decorators = {
        decorator: function(fn, props, container, options) {
          fn.run = options.fn();
          return fn;
        }
      };
      shouldCompileTo(
        '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
        { hash: {}, helpers: helpers, decorators: decorators },
        'success'
      );
    });
    it('should support nested decorators', function() {
      var helpers = {
        helper: function(options) {
          return options.fn.run;
        }
      };
      var decorators = {
        decorator: function(fn, props, container, options) {
          fn.run = options.fn.nested + options.fn();
          return fn;
        },
        nested: function(fn, props, container, options) {
          props.nested = options.fn();
        }
      };
      shouldCompileTo(
        '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
        { hash: {}, helpers: helpers, decorators: decorators },
        'success'
      );
    });

    it('should apply multiple decorators', function() {
      var helpers = {
        helper: function(options) {
          return options.fn.run;
        }
      };
      var decorators = {
        decorator: function(fn, props, container, options) {
          fn.run = (fn.run || '') + options.fn();
          return fn;
        }
      };
      shouldCompileTo(
        '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
        { hash: {}, helpers: helpers, decorators: decorators },
        'success'
      );
    });

    it('should access parent variables', function() {
      var helpers = {
        helper: function(options) {
          return options.fn.run;
        }
      };
      var decorators = {
        decorator: function(fn, props, container, options) {
          fn.run = options.args;
          return fn;
        }
      };
      shouldCompileTo(
        '{{#helper}}{{*decorator foo}}{{/helper}}',
        { hash: { foo: 'success' }, helpers: helpers, decorators: decorators },
        'success'
      );
    });
    it('should work with root program', function() {
      var run;
      var decorators = {
        decorator: function(fn, props, container, options) {
          equals(options.args[0], 'success');
          run = true;
          return fn;
        }
      };
      shouldCompileTo(
        '{{*decorator "success"}}',
        { hash: { foo: 'success' }, decorators: decorators },
        ''
      );
      equals(run, true);
    });
    it('should fail when accessing variables from root', function() {
      var run;
      var decorators = {
        decorator: function(fn, props, container, options) {
          equals(options.args[0], undefined);
          run = true;
          return fn;
        }
      };
      shouldCompileTo(
        '{{*decorator foo}}',
        { hash: { foo: 'fail' }, decorators: decorators },
        ''
      );
      equals(run, true);
    });

    describe('registration', function() {
      it('unregisters', function() {
        handlebarsEnv.decorators = {};

        handlebarsEnv.registerDecorator('foo', function() {
          return 'fail';
        });

        equals(!!handlebarsEnv.decorators.foo, true);
        handlebarsEnv.unregisterDecorator('foo');
        equals(handlebarsEnv.decorators.foo, undefined);
      });

      it('allows multiple globals', function() {
        handlebarsEnv.decorators = {};

        handlebarsEnv.registerDecorator({
          foo: function() {},
          bar: function() {}
        });

        equals(!!handlebarsEnv.decorators.foo, true);
        equals(!!handlebarsEnv.decorators.bar, true);
        handlebarsEnv.unregisterDecorator('foo');
        handlebarsEnv.unregisterDecorator('bar');
        equals(handlebarsEnv.decorators.foo, undefined);
        equals(handlebarsEnv.decorators.bar, undefined);
      });
      it('fails with multiple and args', function() {
        shouldThrow(
          function() {
            handlebarsEnv.registerDecorator(
              {
                world: function() {
                  return 'world!';
                },
                testHelper: function() {
                  return 'found it!';
                }
              },
              {}
            );
          },
          Error,
          'Arg not supported with multiple decorators'
        );
      });
    });
  });
});
