style_test.js

// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.cssom.iframe.styleTest');
goog.setTestOnly('goog.cssom.iframe.styleTest');

goog.require('goog.cssom');
goog.require('goog.cssom.iframe.style');
goog.require('goog.dom');
goog.require('goog.dom.DomHelper');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');

// unit tests
var propertiesToTest = [
  'color',
  'font-family',
  'font-style',
  'font-size',
  'font-variant',
  'border-top-style',
  'border-top-width',
  'border-top-color',
  'background-color',
  'margin-bottom'
];

function crawlDom(startNode, func) {
  if (startNode.nodeType != 1) { return; }
  func(startNode);
  for (var i = 0; i < startNode.childNodes.length; i++) {
    crawlDom(startNode.childNodes[i], func);
  }
}

function getCurrentCssProperties(node, propList) {
  var props = {};
  if (node.nodeType != 1) { return; }
  for (var i = 0; i < propList.length; i++) {
    var prop = propList[i];
    if (node.currentStyle) { // IE
      var propCamelCase = '';
      var propParts = prop.split('-');
      for (var j = 0; j < propParts.length; j++) {
        propCamelCase += propParts[j].charAt(0).toUpperCase() +
                         propParts[j].substring(1, propParts[j].length);
      }
      props[prop] = node.currentStyle[propCamelCase];
    } else { // standards-compliant browsers
      props[prop] = node.ownerDocument.defaultView.getComputedStyle(
          node, '').getPropertyValue(prop);
    }
  }
  return props;
}

function CssPropertyCollector() {
  var propsList = [];
  this.propsList = propsList;

  this.collectProps = function(node) {
    var nodeProps = getCurrentCssProperties(node, propertiesToTest);
    if (nodeProps) { propsList.push([nodeProps, node]); }
  };
}

function recursivelyListCssProperties(el) {
  var collector = new CssPropertyCollector();
  crawlDom(el, collector.collectProps);
  return collector.propsList;
}

function testMatchCssSelector() {
  var container = document.createElement('div');
  container.className = 'container';
  var el = document.createElement('div');
  x = el;
  el.id = 'mydiv';
  el.className = 'colorful foo';
  // set some arbirtrary content
  el.innerHTML = '<div><ul><li>One</li><li>Two</li></ul></div>';
  container.appendChild(el);
  document.body.appendChild(container);

  var elementAncestry = new goog.cssom.iframe.style.NodeAncestry_(el);
  assertEquals(5, elementAncestry.nodes.length);

  // list of input/output results. Output is the index of the selector
  // that we expect to match - for example, in 'body div div.colorful',
  // 'div.colorful' has an index of 2.
  var expectedResults = [
    ['body div', [4, 1]],
    ['h1', null],
    ['body div h1', [4, 1]],
    ['body div.colorful h1', [4, 1]],
    ['body div div', [4, 2]],
    ['body div div div', [4, 2]],
    ['body div div.somethingelse div', [4, 1]],
    ['body div.somethingelse div', [2, 0]],
    ['div.container', [3, 0]],
    ['div.container div', [4, 1]],
    ['#mydiv', [4, 0]],
    ['div#mydiv', [4, 0]],
    ['div.colorful', [4, 0]],
    ['div#mydiv .colorful', [4, 0]],
    ['.colorful', [4, 0]],
    ['body * div', [4, 2]],
    ['body * *', [4, 2]]
  ];
  for (var i = 0; i < expectedResults.length; i++) {
    var input = expectedResults[i][0];
    var expectedResult = expectedResults[i][1];
    var selector = new goog.cssom.iframe.style.CssSelector_(input);
    var result = selector.matchElementAncestry(elementAncestry);
    if (expectedResult == null) {
      assertEquals('Expected null result', expectedResult, result);
    } else {
      assertEquals('Expected element index for ' + input,
                   expectedResult[0],
                   result.elementIndex);
      assertEquals('Expected selector part index for ' + input,
                   expectedResult[1],
                   result.selectorPartIndex);
    }
  }
  document.body.removeChild(container);
}

function makeIframeDocument(iframe) {
  var doc = goog.dom.getFrameContentDocument(iframe);
  doc.open();
  doc.write('<html><head>');
  doc.write('<style>html,body { background-color: transparent; }</style>');
  doc.write('</head><body></body></html>');
  doc.close();
  return doc;
}

function testCopyCss() {
  for (var i = 1; i <= 4; i++) {
    var sourceElement = document.getElementById('source' + i);
    var newFrame = document.createElement('iframe');
    newFrame.allowTransparency = true;
    sourceElement.parentNode.insertBefore(newFrame,
                                          sourceElement.nextSibling);
    var doc = makeIframeDocument(newFrame);
    goog.cssom.addCssText(
        goog.cssom.iframe.style.getElementContext(sourceElement),
        new goog.dom.DomHelper(doc));
    doc.body.innerHTML = sourceElement.innerHTML;

    var oldProps = recursivelyListCssProperties(sourceElement);
    var newProps = recursivelyListCssProperties(doc.body);

    assertEquals(oldProps.length, newProps.length);
    for (var j = 0; j < oldProps.length; j++) {
      for (var k = 0; k < propertiesToTest.length; k++) {
        assertEquals('testing property ' + propertiesToTest[k],
                     oldProps[j][0][propertiesToTest[k]],
                     newProps[j][0][propertiesToTest[k]]);
      }
    }
  }
}

function normalizeCssText(cssText) {
  // Normalize cssText for testing purposes.
  return cssText.replace(/\s/g, '').toLowerCase();
}

function testAImportantInFF2() {
  var testDiv = document.getElementById('source1');
  var cssText = normalizeCssText(
      goog.cssom.iframe.style.getElementContext(testDiv));
  var color = standardizeCSSValue('color', 'red');
  var NORMAL_RULE = 'a{color:' + color;
  var FF_2_RULE = 'a{color:' + color + '!important';
  if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9a')) {
    assertContains(FF_2_RULE, cssText);
  } else {
    assertContains(NORMAL_RULE, cssText);
    assertNotContains(FF_2_RULE, cssText);
  }
}

function testCopyBackgroundContext() {
  var testDiv = document.getElementById('backgroundTest');
  var cssText = goog.cssom.iframe.style.getElementContext(testDiv,
                                                          null,
                                                          true);
  var iframe = document.createElement('iframe');
  var ancestor = document.getElementById('backgroundTest-ancestor-1');
  ancestor.parentNode.insertBefore(iframe, ancestor.nextSibling);
  iframe.style.width = '100%';
  iframe.style.height = '100px';
  iframe.style.borderWidth = '0px';
  var doc = makeIframeDocument(iframe);
  goog.cssom.addCssText(cssText, new goog.dom.DomHelper(doc));
  doc.body.innerHTML = testDiv.innerHTML;
  var normalizedCssText = normalizeCssText(cssText);
  assertTrue(
      'Background color should be copied from parent element',
      /body{[^{]*background-color:(?:rgb\(128,0,128\)|#800080)/.test(
          normalizedCssText));
  assertTrue(
      'Background image should be copied from ancestor element',
      /body{[^{]*background-image:url\(/.test(normalizedCssText));
  // Background-position can't be calculated in FF2, due to this bug:
  // http://bugzilla.mozilla.org/show_bug.cgi?id=316981
  if (!(goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9'))) {
    // Expected x position is:
    // originalBackgroundPositionX - elementOffsetLeft
    // 40px - (1px + 8px) == 31px
    // Expected y position is:
    // originalBackgroundPositionY - elementOffsetLeft
    // 70px - (1px + 10px + 5px) == 54px;
    assertTrue('Background image position should be adjusted correctly',
        /body{[^{]*background-position:31px54px/.test(normalizedCssText));
  }
}

function testCopyBackgroundContextFromIframe() {
  var testDiv = document.getElementById('backgroundTest');
  var iframe = document.createElement('iframe');
  iframe.allowTransparency = true;
  iframe.style.position = 'absolute';
  iframe.style.top = '5px';
  iframe.style.left = '5px';
  iframe.style.borderWidth = '2px';
  iframe.style.borderStyle = 'solid';
  testDiv.appendChild(iframe);
  var doc = makeIframeDocument(iframe);
  doc.body.backgroundColor = 'transparent';
  doc.body.style.margin = '0';
  doc.body.style.padding = '0';
  doc.body.innerHTML = '<p style="margin: 0">I am transparent!</p>';
  var normalizedCssText = normalizeCssText(
      goog.cssom.iframe.style.getElementContext(
          doc.body.firstChild, null, true));
  // Background properties should get copied through from the parent
  // document since the iframe is transparent
  assertTrue(
      'Background color should be copied from parent element',
      /body{[^{]*background-color:(?:rgb\(128,0,128\)|#800080)/.test(
          normalizedCssText));
  assertTrue(
      'Background image should be copied from ancestor element',
      /body{[^{]*background-image:url\(/.test(normalizedCssText));
  // Background-position can't be calculated in FF2, due to this bug:
  // http://bugzilla.mozilla.org/show_bug.cgi?id=316981
  if (!(goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9'))) {
    // Image offset should have been calculated to be the same as the
    // above example, but adding iframe offset and borderWidth.
    // Expected x position is:
    // originalBackgroundPositionX - elementOffsetLeft
    // 40px - (1px + 8px + 5px + 2px) == 24px
    // Expected y position is:
    // originalBackgroundPositionY - elementOffsetLeft
    // 70px - (1px + 10px + 5px + 5px + 2px) == 47px;
    assertTrue('Background image position should be adjusted correctly',
        !!/body{[^{]*background-position:24px47px/.exec(
            normalizedCssText));
  }
  iframe.parentNode.removeChild(iframe);
}

function testCopyFontFaceRules() {
  var isFontFaceCssomSupported =
      goog.userAgent.WEBKIT ||
      goog.userAgent.OPERA ||
      (goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'));
  // We cannot use goog.testing.ExpectedFailures since it dynamically
  // brings in CSS which causes the background context tests to fail
  // in IE6.
  if (isFontFaceCssomSupported) {
    var cssText = goog.cssom.iframe.style.getElementContext(
        document.getElementById('cavalier'));
    assertTrue('The font face rule should have been copied correctly',
               /@font-face/.test(cssText));
  }
}