cssom_test.js

// Copyright 2008 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.cssomTest');
goog.setTestOnly('goog.cssomTest');

goog.require('goog.array');
goog.require('goog.cssom');
goog.require('goog.cssom.CssRuleType');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');

// Since sheet cssom_test1.css's first line is to import
// cssom_test2.css, we should get 2 before one in the string.
var cssText = '.css-link-1 { display: block; } ' +
    '.css-import-2 { display: block; } ' +
    '.css-import-1 { display: block; } ' +
    '.css-style-1 { display: block; } ' +
    '.css-style-2 { display: block; } ' +
    '.css-style-3 { display: block; }';

var replacementCssText = '.css-repl-1 { display: block; }';

var isIe7 = goog.userAgent.IE &&
    (goog.userAgent.compare(goog.userAgent.VERSION, '7.0') == 0);

// We're going to toLowerCase cssText before testing, because IE returns
// CSS property names in UPPERCASE, and the function shouldn't
// "fix" the text as it would be expensive and rarely of use.
// Same goes for the trailing whitespace in IE.
// Same goes for fixing the optimized removal of trailing ; in rules.
// Also needed for Opera.
function fixCssTextForIe(cssText) {
  cssText = cssText.toLowerCase().replace(/\s*$/, '');
  if (cssText.match(/[^;] \}/)) {
    cssText = cssText.replace(/([^;]) \}/g, '$1; }');
  }
  return cssText;
}

function testGetFileNameFromStyleSheet() {
  var styleSheet = {'href': 'http://foo.com/something/filename.css'};
  assertEquals('filename.css',
      goog.cssom.getFileNameFromStyleSheet(styleSheet));

  styleSheet = {'href': 'https://foo.com:123/something/filename.css'};
  assertEquals('filename.css',
      goog.cssom.getFileNameFromStyleSheet(styleSheet));

  styleSheet = {'href': 'http://foo.com/something/filename.css?bar=bas'};
  assertEquals('filename.css',
      goog.cssom.getFileNameFromStyleSheet(styleSheet));

  styleSheet = {'href': 'filename.css?bar=bas'};
  assertEquals('filename.css',
      goog.cssom.getFileNameFromStyleSheet(styleSheet));

  styleSheet = {'href': 'filename.css'};
  assertEquals('filename.css',
      goog.cssom.getFileNameFromStyleSheet(styleSheet));
}

function testGetAllCssStyleSheets() {
  var styleSheets = goog.cssom.getAllCssStyleSheets();
  assertEquals(4, styleSheets.length);
  // Makes sure they're in the right cascade order.
  assertEquals('cssom_test_link_1.css',
      goog.cssom.getFileNameFromStyleSheet(styleSheets[0]));
  assertEquals('cssom_test_import_2.css',
      goog.cssom.getFileNameFromStyleSheet(styleSheets[1]));
  assertEquals('cssom_test_import_1.css',
      goog.cssom.getFileNameFromStyleSheet(styleSheets[2]));
  // Not an external styleSheet
  assertNull(goog.cssom.getFileNameFromStyleSheet(styleSheets[3]));
}

function testGetAllCssText() {
  var allCssText = goog.cssom.getAllCssText();
  // In IE7, a CSSRule object gets included twice and replaces another
  // existing CSSRule object. We aren't using
  // goog.testing.ExpectedFailures since it brings in additional CSS
  // which breaks a lot of our expectations about the number of rules
  // present in a style sheet.
  if (!isIe7) {
    assertEquals(cssText, fixCssTextForIe(allCssText));
  }
}

function testGetAllCssStyleRules() {
  var allCssRules = goog.cssom.getAllCssStyleRules();
  assertEquals(6, allCssRules.length);
}


function testAddCssText() {
  var newCssText = '.css-add-1 { display: block; }';
  var newCssNode = goog.cssom.addCssText(newCssText);

  assertEquals(document.styleSheets.length, 3);

  var allCssText = goog.cssom.getAllCssText();

  // In IE7, a CSSRule object gets included twice and replaces another
  // existing CSSRule object. We aren't using
  // goog.testing.ExpectedFailures since it brings in additional CSS
  // which breaks a lot of our expectations about the number of rules
  // present in a style sheet.
  if (!isIe7) {
    // Opera inserts the CSSRule to the first position. And fixCssText
    // is also needed to clean up whitespace.
    if (goog.userAgent.OPERA) {
      assertEquals(newCssText + ' ' + cssText,
                   fixCssTextForIe(allCssText));
    } else {
      assertEquals(cssText + ' ' + newCssText,
                   fixCssTextForIe(allCssText));
    }
  }

  var cssRules = goog.cssom.getAllCssStyleRules();
  assertEquals(7, cssRules.length);

  // Remove the new stylesheet now so it doesn't interfere with other
  // tests.
  newCssNode.parentNode.removeChild(newCssNode);
  // Sanity check.
  cssRules = goog.cssom.getAllCssStyleRules();
  assertEquals(6, cssRules.length);
}

function testAddCssRule() {
  // test that addCssRule correctly adds the rule to the style
  // sheet.
  var styleSheets = goog.cssom.getAllCssStyleSheets();
  var styleSheet = styleSheets[3];
  var newCssRule = '.css-addCssRule { display: block; }';
  var rules = styleSheet.rules || styleSheet.cssRules;
  var origNumberOfRules = rules.length;

  goog.cssom.addCssRule(styleSheet, newCssRule, 1);

  rules = styleSheet.rules || styleSheet.cssRules;
  var newNumberOfRules = rules.length;
  assertEquals(newNumberOfRules, origNumberOfRules + 1);

  // Remove the added rule so we don't mess up other tests.
  goog.cssom.removeCssRule(styleSheet, 1);
}

function testAddCssRuleAtPos() {
  // test that addCssRule correctly adds the rule to the style
  // sheet at the specified position.
  var styleSheets = goog.cssom.getAllCssStyleSheets();
  var styleSheet = styleSheets[3];
  var newCssRule = '.css-addCssRulePos { display: block; }';
  var rules = styleSheet.cssRules || styleSheet.rules;
  var origNumberOfRules = rules.length;

  // Firefox croaks if we try to insert a CSSRule at an index that
  // contains a CSSImport Rule. Since we deal only with CSSStyleRule
  // objects, we find the first CSSStyleRule and return its index.
  //
  // NOTE(user): We could have unified the code block below for all
  // browsers but IE6 horribly mangled up the stylesheet by creating
  // duplicate instances of a rule when removeCssRule was invoked
  // just after addCssRule with the looping construct in. This is
  // perfectly fine since IE's styleSheet.rules does not contain
  // references to anything but CSSStyleRules.
  var pos = 0;
  if (styleSheet.cssRules) {
    pos = goog.array.findIndex(rules, function(rule) {
      return rule.type == goog.cssom.CssRuleType.STYLE;
    });
  }
  goog.cssom.addCssRule(styleSheet, newCssRule, pos);

  rules = styleSheet.cssRules || styleSheet.rules;
  var newNumberOfRules = rules.length;
  assertEquals(newNumberOfRules, origNumberOfRules + 1);

  // Remove the added rule so we don't mess up other tests.
  goog.cssom.removeCssRule(styleSheet, pos);

  rules = styleSheet.cssRules || styleSheet.rules;
  assertEquals(origNumberOfRules, rules.length);
}

function testAddCssRuleNoIndex() {
  // How well do we handle cases where the optional index is
  //  not passed in?
  var styleSheets = goog.cssom.getAllCssStyleSheets();
  var styleSheet = styleSheets[3];
  var rules = styleSheet.cssRules || styleSheet.rules;
  var origNumberOfRules = rules.length;
  var newCssRule = '.css-addCssRuleNoIndex { display: block; }';

  // Try inserting the rule without specifying an index.
  // Make sure we don't throw an exception, and that we added
  // the entry.
  goog.cssom.addCssRule(styleSheet, newCssRule);

  rules = styleSheet.cssRules || styleSheet.rules;
  var newNumberOfRules = rules.length;
  assertEquals(newNumberOfRules, origNumberOfRules + 1);

  // Remove the added rule so we don't mess up the other tests.
  goog.cssom.removeCssRule(styleSheet, newNumberOfRules - 1);

  rules = styleSheet.cssRules || styleSheet.rules;
  assertEquals(origNumberOfRules, rules.length);
}


function testGetParentStyleSheetAfterGetAllCssStyleRules() {
  var cssRules = goog.cssom.getAllCssStyleRules();
  var cssRule = cssRules[4];
  var parentStyleSheet = goog.cssom.getParentStyleSheet(cssRule);
  var styleSheets = goog.cssom.getAllCssStyleSheets();
  var styleSheet = styleSheets[3];
  assertEquals(styleSheet, parentStyleSheet);
}

function testGetCssRuleIndexInParentStyleSheetAfterGetAllCssStyleRules() {
  var cssRules = goog.cssom.getAllCssStyleRules();
  var cssRule = cssRules[4];
  // Note here that this is correct - IE's styleSheet.rules does not
  // contain references to anything but CSSStyleRules while FF and others
  // include anything that inherits from the CSSRule interface.
  // See http://dev.w3.org/csswg/cssom/#cssrule.
  var ruleIndex = goog.userAgent.IE ? 1 : 2;
  assertEquals(ruleIndex,
      goog.cssom.getCssRuleIndexInParentStyleSheet(cssRule));
}

function testGetCssRuleIndexInParentStyleSheetNonStyleRule() {
  // IE's styleSheet.rules only contain CSSStyleRules.
  if (!goog.userAgent.IE) {
    var styleSheets = goog.cssom.getAllCssStyleSheets();
    var styleSheet = styleSheets[3];
    var newCssRule = '@media print { .css-nonStyle { display: block; } }';
    goog.cssom.addCssRule(styleSheet, newCssRule);
    var rules = styleSheet.rules || styleSheet.cssRules;
    var cssRule = rules[rules.length - 1];
    assertEquals(goog.cssom.CssRuleType.MEDIA, cssRule.type);
    // Make sure we don't throw an exception.
    goog.cssom.getCssRuleIndexInParentStyleSheet(cssRule, styleSheet);
    // Remove the added rule.
    goog.cssom.removeCssRule(styleSheet, rules.length - 1);
  }
}

// Tests the scenario where we have a known stylesheet and index.
function testReplaceCssRuleWithStyleSheetAndIndex() {
  var styleSheets = goog.cssom.getAllCssStyleSheets();
  var styleSheet = styleSheets[3];
  var rules = goog.cssom.getCssRulesFromStyleSheet(styleSheet);
  var index = 2;
  var origCssRule = rules[index];
  var origCssText =
      fixCssTextForIe(goog.cssom.getCssTextFromCssRule(origCssRule));

  goog.cssom.replaceCssRule(origCssRule, replacementCssText, styleSheet,
      index);

  var rules = goog.cssom.getCssRulesFromStyleSheet(styleSheet);
  var newCssRule = rules[index];
  var newCssText = goog.cssom.getCssTextFromCssRule(newCssRule);
  assertEquals(replacementCssText, fixCssTextForIe(newCssText));

  // Now we need to re-replace our rule, to preserve parity for the other
  // tests.
  goog.cssom.replaceCssRule(newCssRule, origCssText, styleSheet, index);
  var rules = goog.cssom.getCssRulesFromStyleSheet(styleSheet);
  var nowCssRule = rules[index];
  var nowCssText = goog.cssom.getCssTextFromCssRule(nowCssRule);
  assertEquals(origCssText, fixCssTextForIe(nowCssText));
}

function testReplaceCssRuleUsingGetAllCssStyleRules() {
  var cssRules = goog.cssom.getAllCssStyleRules();
  var origCssRule = cssRules[4];
  var origCssText =
      fixCssTextForIe(goog.cssom.getCssTextFromCssRule(origCssRule));
  // notice we don't pass in the stylesheet or index.
  goog.cssom.replaceCssRule(origCssRule, replacementCssText);

  var styleSheets = goog.cssom.getAllCssStyleSheets();
  var styleSheet = styleSheets[3];
  var rules = goog.cssom.getCssRulesFromStyleSheet(styleSheet);
  var index = goog.userAgent.IE ? 1 : 2;
  var newCssRule = rules[index];
  var newCssText =
      fixCssTextForIe(goog.cssom.getCssTextFromCssRule(newCssRule));
  assertEquals(replacementCssText, newCssText);

  // try getting it the other way around too.
  var cssRules = goog.cssom.getAllCssStyleRules();
  var newCssRule = cssRules[4];
  var newCssText =
      fixCssTextForIe(goog.cssom.getCssTextFromCssRule(newCssRule));
  assertEquals(replacementCssText, newCssText);

  // Now we need to re-replace our rule, to preserve parity for the other
  // tests.
  goog.cssom.replaceCssRule(newCssRule, origCssText);
  var cssRules = goog.cssom.getAllCssStyleRules();
  var nowCssRule = cssRules[4];
  var nowCssText =
      fixCssTextForIe(goog.cssom.getCssTextFromCssRule(nowCssRule));
  assertEquals(origCssText, nowCssText);
}