describe('DomHelper', function () {
  var DomHelper = webfont.DomHelper,
      domHelper = new DomHelper(window);

  describe('#createElement', function () {
    it('should create an element', function () {
      var div = domHelper.createElement('div');

      expect(div).not.toBeNull();
    });

    it('should create an element with inline content', function () {
      var div = domHelper.createElement('div', {}, 'moo');

      expect(div).not.toBeNull();
      expect(div.innerHTML).toEqual('moo');
    });

    it('should create an element with attributes and inline content', function () {
      var div = domHelper.createElement('div', {
            style: 'font-size: 42px',
            id: 'mySpan'
          }, 'hello');

      expect(div).not.toBeNull();
      expect(div.innerHTML).toEqual('hello');
      expect(div.style.fontSize).toEqual('42px');
      expect(div.id).toEqual('mySpan');
    });

    it('should work with augmented Object.prototype', function () {
      Object.prototype.evil = function () {};

      var div = domHelper.createElement('div', { id: 'augmented' });
      var parentDiv = domHelper.createElement('div', { id: 'parentaugmented' });

      parentDiv.appendChild(div);

      expect(div).not.toBeNull();
      expect(!!div.getAttribute('evil')).toBe(false);
      expect(-1, parentDiv.innerHTML.indexOf('evil'));

      delete Object.prototype.evil;
    });
  });

  describe('#appendClassName', function () {
    it('should have added a class name', function () {
      var div = domHelper.createElement('div');
      domHelper.appendClassName(div, 'moo');

      expect(div.className).toEqual('moo');
    });

    it('should not add duplicate class names', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'moo');
      domHelper.appendClassName(div, 'meu');
      domHelper.appendClassName(div, 'moo');

      expect(div.className).toEqual('moo meu');
    });

    it('should add multiple class names', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'moo meu moo');
      expect(div.className).toEqual('moo meu moo');
    });

    it('should normalize spaces and tabs', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'meu');
      domHelper.appendClassName(div, '      foo ');
      expect(div.className).toEqual('meu foo');
    });
  });

  describe('#removeClassName', function () {
    it('should remove class names', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'meu moo');
      expect(div.className).toEqual('meu moo');

      domHelper.removeClassName(div, 'meu');
      expect(div.className).toEqual('moo');
    });

    it('should not remove non-existing classes', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'moo');
      expect(div.className).toEqual('moo');
      domHelper.removeClassName(div, 'boo');
      expect(div.className).toEqual('moo');
    });
  });

  describe('#updateClassName', function () {
    it('should handle optional arguments correctly', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'moo');

      domHelper.updateClassName(div);
      expect(div.className).toEqual('moo');

      domHelper.updateClassName(div, [], []);
      expect(div.className).toEqual('moo');

      domHelper.updateClassName(div, null, null);
      expect(div.className).toEqual('moo');
    });

    it('should have added a class name', function () {
      var div = domHelper.createElement('div');
      domHelper.updateClassName(div, ['moo']);

      expect(div.className).toEqual('moo');
    });

    it('should not add duplicate class names', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'moo');
      domHelper.updateClassName(div, ['moo']);

      expect(div.className).toEqual('moo');
    });

    it('should add multiple class names', function () {
      var div = domHelper.createElement('div');

      domHelper.updateClassName(div, ['moo', 'meu', 'moo']);
      expect(div.className).toEqual('moo meu');
    });

    it('should normalize spaces and tabs', function () {
      var div = domHelper.createElement('div');

      domHelper.updateClassName(div, ['meu', '      foo']);
      expect(div.className).toEqual('meu foo');
    });

    it('should remove class names', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'meu moo');
      expect(div.className).toEqual('meu moo');

      domHelper.updateClassName(div, null, ['meu']);
      expect(div.className).toEqual('moo');
    });

    it('should remove multiple class names', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'meu');
      domHelper.appendClassName(div, 'moo');
      expect(div.className).toEqual('meu moo');

      domHelper.updateClassName(div, null, ['meu', 'moo']);
      expect(div.className).toEqual('');
    });

    it('should not remove non-existing classes', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'moo');
      expect(div.className).toEqual('moo');

      domHelper.updateClassName(div, null, 'boo');
      expect(div.className).toEqual('moo');
    });

    it('should add and remove class names', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'moo');
      domHelper.appendClassName(div, 'meh');
      expect(div.className).toEqual('moo meh');

      domHelper.updateClassName(div, ['meu'], ['moo', 'meh']);
      expect(div.className).toEqual('meu');
    });

    it('should update one of many class names', function () {
      var div = domHelper.createElement('div');

      domHelper.appendClassName(div, 'moo');
      domHelper.appendClassName(div, 'meh');
      expect(div.className).toEqual('moo meh');

      domHelper.updateClassName(div, ['meu'], ['moo']);
      expect(div.className).toEqual('meh meu');
    });
  });

  describe('#hasClassName', function () {
    var div = null;

    beforeEach(function () {
       div = domHelper.createElement('div');
       domHelper.appendClassName(div, 'moo moo-meu');
    });

    it('should return true', function () {
      expect(domHelper.hasClassName(div, 'moo')).toBe(true);
      expect(domHelper.hasClassName(div, 'moo-meu')).toBe(true);
    });

    it('should return false', function () {
      expect(domHelper.hasClassName(div, 'boo')).toBe(false);
      expect(domHelper.hasClassName(div, 'meu')).toBe(false);
    });
  });

  describe('#setStyle', function () {
    var div = null;

    beforeEach(function () {
      div = domHelper.createElement('div');
    });

    it('should set the style correctly', function () {
      domHelper.setStyle(div, 'left:3px;top:1px;');
      expect(div.style.left).toEqual('3px');
      expect(div.style.top).toEqual('1px');
    });
  });

  describe('#createStyle', function () {
    var style = null;

    beforeEach(function () {
      style = domHelper.createStyle('blockquote{font-size:300px}');
      domHelper.insertInto('head', style);
    });

    afterEach(function () {
      domHelper.removeElement(style);
    });

    it('should create a style element', function () {
      expect(style).not.toBeNull();
      expect(style.nodeName).toEqual('STYLE');
    });

    it('should set the css content correctly', function () {
      var text = style.styleSheet ? style.styleSheet.cssText : style.textContent;
      expect(text.replace(/[\s;]/g, '').toLowerCase()).toEqual('blockquote{font-size:300px}');
    });
  });

  describe('#loadStylesheet', function () {
    it('should load the stylesheet', function () {
      var el = null,
          width = null,
          link = null;

      runs(function () {
        el = domHelper.createElement('div', { id: 'TEST_ELEMENT' });
        domHelper.insertInto('body', el);
        width = el.offsetWidth;
        link = domHelper.loadStylesheet('fixtures/external_stylesheet.css');
      });

      waitsFor(function () {
        return width !== el.offsetWidth;
      });

      runs(function () {
        expect(link).not.toBeNull();
        expect(link.rel).toEqual('stylesheet');
        expect(el.offsetWidth).toEqual(300);
      });
    });
  });

  describe('#loadStylesheet with callback', function () {
    it('should load the stylesheet', function () {
      var el = null,
          width = null,
          callbackMade = false;

      function callback() {
        callbackMade = true;
      }

      runs(function () {
        el = domHelper.createElement('div', { id: 'TEST_ELEMENT' });
        domHelper.insertInto('body', el);
        width = el.offsetWidth;
        domHelper.loadStylesheet('fixtures/external_stylesheet.css', callback);
      });

      waitsFor(function () {
        return callbackMade;
      });

      runs(function () {
        expect(el.offsetWidth).toEqual(300);
      });
    });
  });

  describe('#loadStylesheet with async and callback', function () {
    it('should load the stylesheet', function () {
      var el = null,
          width = null,
          callbackMade = false;

      function callback() {
        callbackMade = true;
      }

      runs(function () {
        el = domHelper.createElement('div', { id: 'TEST_ELEMENT' });
        domHelper.insertInto('body', el);
        width = el.offsetWidth;
        domHelper.loadStylesheet('fixtures/external_stylesheet.css', callback, true);
      });

      waitsFor(function () {
        return callbackMade;
      });

      runs(function () {
        expect(el.offsetWidth).toEqual(300);
      });
    });
  });

  describe('#loadScript', function () {
    it('should load the script', function () {
      runs(function () {
        domHelper.loadScript('fixtures/external_script.js');
      });

      waitsFor(function () {
        return window.EXTERNAL_SCRIPT_LOADED;
      }, 'script was never inserted', 1000);

      runs(function () {
        expect(window.EXTERNAL_SCRIPT_LOADED).toBe(true);
      });
    });

    it('should call the callback', function () {
      var called = false,
          error = null;

      runs(function () {
        domHelper.loadScript('fixtures/external_script.js', function (err) {
          called = true;
          error = err;
        });
      });

      waitsFor(function () {
        return called;
      }, 'callback was never called', 1000);

      runs(function () {
        expect(called).toBe(true);
        expect(error).toBeFalsy();
      });
    });

    it('should return a script element', function () {
      var script = domHelper.loadScript('fixtures/external_script.js');

      expect(script).not.toBeNull();
      expect(script.nodeName).toEqual('SCRIPT');
    });

    it('should timeout if the script does not load or is very slow', function () {
      var called = false,
          error = false;

      // Spy on createElement so the all loadScript code is executed but
      // the "script" won't actually load.
      spyOn(domHelper, 'createElement').andCallFake(function (name) {
        return document.createElement('div');
      });

      runs(function () {
        domHelper.loadScript('fixtures/external_script.js', function (err) {
          called = true;
          error = err;
        }, 100);
      });

      waitsFor(function () {
        return called;
      });

      runs(function () {
        expect(called).toBe(true);
        expect(error).toBeTruthy();
      });
    });
  });

  describe('#getProtocol', function () {
    it('should return http', function () {
      var domHelper = new DomHelper({
        location: {
          protocol: 'http:'
        }
      });

      expect(domHelper.getProtocol()).toEqual('http:');
    });

    it('should return https', function () {
      var domHelper = new DomHelper({
        location: {
          protocol: 'https:'
        }
      });

      expect(domHelper.getProtocol()).toEqual('https:');
    });

    it('should return the protocol from an iframe', function () {
      var domHelper = new DomHelper({
        location: {
          protocol: 'https:'
        }
      }, {
        location: {
          protocol: 'http:'
        }
      });

      expect(domHelper.getProtocol()).toEqual('http:');
    });

    it('should return the protocol from the main window if the iframe has no protocol', function () {
      var domHelper = new DomHelper({
        location: {
          protocol: 'http:'
        }
      }, {
        location: {
          protocol: 'about:'
        }
      });

      expect(domHelper.getProtocol()).toEqual('http:');
    });
  });

  describe('#setProtocol', function () {
    it('ignores invalid values', function () {
      var domHelper = new DomHelper({
        location: 'http:'
      });

      domHelper.setProtocol('huh');

      expect(domHelper.getProtocol()).toEqual('http:');
    });

    it('overrides the global value', function () {
      var domHelper = new DomHelper({
        location: 'http:'
      });

      domHelper.setProtocol('https:');

      expect(domHelper.getProtocol()).toEqual('https:');
    });
  });

  describe('#isHttps', function () {
    it('should return true if the protocol is https', function () {
      var domHelper = new DomHelper({
        location: {
          protocol: 'https:'
        }
      });

      expect(domHelper.isHttps()).toBe(true);
    });

    it('should return false if the protocol is not https', function () {
      var domHelper = new DomHelper({
        location: {
          protocol: 'http:'
        }
      });

      expect(domHelper.isHttps()).toBe(false);
    });
  });

  describe('#getHostname', function () {
    it('should return the hostname', function () {
      var domHelper = new DomHelper({
        location: {
          hostname: 'example.com'
        }
      });

      expect(domHelper.getHostName()).toEqual('example.com');
    });

    it('should return the hostname from the iframe if present', function () {
      var domHelper = new DomHelper({
        location: {
          hostname: 'example.com'
        }
      }, {
        location: {
          hostname: 'example.org'
        }
      });

      expect(domHelper.getHostName()).toEqual('example.org');
    });
  });

  describe('#insertInto', function () {
    it('should insert an element', function () {
      var a = domHelper.createElement('div');

      var result = domHelper.insertInto('body', a);

      expect(result).toBe(true);
      expect(a.parentNode.nodeName).toEqual('BODY');
    });
  });

  describe('#whenBodyExists', function () {
    var domHelper = null,
        callback = null;

    beforeEach(function () {
      domHelper = new DomHelper({
        document: {
          addEventListener: function (event, callback) {
            function check() {
              if (domHelper.document_.body) {
                callback();
              } else {
                setTimeout(check, 10);
              }
            }
            check();
          }
        }
      });

      callback = jasmine.createSpy('callback');
    });

    it('should wait until the body exists before calling the callback', function () {
      runs(function () {
        domHelper.whenBodyExists(callback);
      });

      waits(200);

      runs(function () {
        domHelper.document_.body = true;
      });

      waitsFor(function () {
        return callback.wasCalled;
      }, 'callback was never called', 100);

      runs(function () {
        expect(callback).toHaveBeenCalled();
      });
    });

    it('should not call the callback if the body is not available', function () {
      runs(function () {
        domHelper.whenBodyExists(callback);
      });

      waits(100);

      runs(function () {
        expect(callback).not.toHaveBeenCalled();
      });
    });
  });

  describe('#removeElement', function () {
    it('should remove an element', function () {
      var a = domHelper.createElement('div'),
          b = domHelper.createElement('div');

      a.appendChild(b);

      var result = domHelper.removeElement(b);
      expect(result).toBe(true);
      expect(b.parentNode).not.toEqual(a);
    });

    it('should return false when failing to remove an element', function () {
      var a = domHelper.createElement('div');

      var result = domHelper.removeElement(a);

      expect(result).toBe(false);
    });
  });

  describe('#getMainWindow', function () {
    it('should return the main window', function () {
      var domHelper = new DomHelper(1, 2);
      expect(domHelper.getMainWindow()).toEqual(1);
    });
  });

  describe('#getLoadWindow', function () {
    it('should return the load window', function () {
      var domHelper = new DomHelper(1, 2);
      expect(domHelper.getLoadWindow()).toEqual(2);
    });
  });
});