dom.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Testing utilities for DOM related tests.
  16. *
  17. * @author robbyw@google.com (Robby Walker)
  18. */
  19. goog.setTestOnly('goog.testing.dom');
  20. goog.provide('goog.testing.dom');
  21. goog.require('goog.array');
  22. goog.require('goog.asserts');
  23. goog.require('goog.dom');
  24. goog.require('goog.dom.InputType');
  25. goog.require('goog.dom.NodeIterator');
  26. goog.require('goog.dom.NodeType');
  27. goog.require('goog.dom.TagIterator');
  28. goog.require('goog.dom.TagName');
  29. goog.require('goog.dom.classlist');
  30. goog.require('goog.iter');
  31. goog.require('goog.object');
  32. goog.require('goog.string');
  33. goog.require('goog.style');
  34. goog.require('goog.testing.asserts');
  35. goog.require('goog.userAgent');
  36. goog.forwardDeclare('goog.dom.AbstractRange');
  37. /**
  38. * @return {!Node} A DIV node with a unique ID identifying the
  39. * {@code END_TAG_MARKER_}.
  40. * @private
  41. */
  42. goog.testing.dom.createEndTagMarker_ = function() {
  43. var marker = goog.dom.createElement(goog.dom.TagName.DIV);
  44. marker.id = goog.getUid(marker);
  45. return marker;
  46. };
  47. /**
  48. * A unique object to use as an end tag marker.
  49. * @private {!Node}
  50. * @const
  51. */
  52. goog.testing.dom.END_TAG_MARKER_ = goog.testing.dom.createEndTagMarker_();
  53. /**
  54. * Tests if the given iterator over nodes matches the given Array of node
  55. * descriptors. Throws an error if any match fails.
  56. * @param {goog.iter.Iterator} it An iterator over nodes.
  57. * @param {Array<Node|number|string>} array Array of node descriptors to match
  58. * against. Node descriptors can be any of the following:
  59. * Node: Test if the two nodes are equal.
  60. * number: Test node.nodeType == number.
  61. * string starting with '#': Match the node's id with the text
  62. * after "#".
  63. * other string: Match the text node's contents.
  64. */
  65. goog.testing.dom.assertNodesMatch = function(it, array) {
  66. var i = 0;
  67. goog.iter.forEach(it, function(node) {
  68. if (array.length <= i) {
  69. fail(
  70. 'Got more nodes than expected: ' +
  71. goog.testing.dom.describeNode_(node));
  72. }
  73. var expected = array[i];
  74. if (goog.dom.isNodeLike(expected)) {
  75. assertEquals('Nodes should match at position ' + i, expected, node);
  76. } else if (goog.isNumber(expected)) {
  77. assertEquals(
  78. 'Node types should match at position ' + i, expected, node.nodeType);
  79. } else if (expected.charAt(0) == '#') {
  80. assertEquals(
  81. 'Expected element at position ' + i, goog.dom.NodeType.ELEMENT,
  82. node.nodeType);
  83. var expectedId = expected.substr(1);
  84. assertEquals('IDs should match at position ' + i, expectedId, node.id);
  85. } else {
  86. assertEquals(
  87. 'Expected text node at position ' + i, goog.dom.NodeType.TEXT,
  88. node.nodeType);
  89. assertEquals(
  90. 'Node contents should match at position ' + i, expected,
  91. node.nodeValue);
  92. }
  93. i++;
  94. });
  95. assertEquals('Used entire match array', array.length, i);
  96. };
  97. /**
  98. * Exposes a node as a string.
  99. * @param {Node} node A node.
  100. * @return {string} A string representation of the node.
  101. */
  102. goog.testing.dom.exposeNode = function(node) {
  103. return (node.tagName || node.nodeValue) + (node.id ? '#' + node.id : '') +
  104. ':"' + (node.innerHTML || '') + '"';
  105. };
  106. /**
  107. * Exposes the nodes of a range wrapper as a string.
  108. * @param {goog.dom.AbstractRange} range A range.
  109. * @return {string} A string representation of the range.
  110. */
  111. goog.testing.dom.exposeRange = function(range) {
  112. // This is deliberately not implemented as
  113. // goog.dom.AbstractRange.prototype.toString, because it is non-authoritative.
  114. // Two equivalent ranges may have very different exposeRange values, and
  115. // two different ranges may have equal exposeRange values.
  116. // (The mapping of ranges to DOM nodes/offsets is a many-to-many mapping).
  117. if (!range) {
  118. return 'null';
  119. }
  120. return goog.testing.dom.exposeNode(range.getStartNode()) + ':' +
  121. range.getStartOffset() + ' to ' +
  122. goog.testing.dom.exposeNode(range.getEndNode()) + ':' +
  123. range.getEndOffset();
  124. };
  125. /**
  126. * Determines if the current user agent matches the specified string. Returns
  127. * false if the string does specify at least one user agent but does not match
  128. * the running agent.
  129. * @param {string} userAgents Space delimited string of user agents.
  130. * @return {boolean} Whether the user agent was matched. Also true if no user
  131. * agent was listed in the expectation string.
  132. * @private
  133. */
  134. goog.testing.dom.checkUserAgents_ = function(userAgents) {
  135. if (goog.string.startsWith(userAgents, '!')) {
  136. if (goog.string.contains(userAgents, ' ')) {
  137. throw new Error('Only a single negative user agent may be specified');
  138. }
  139. return !goog.userAgent[userAgents.substr(1)];
  140. }
  141. var agents = userAgents.split(' ');
  142. var hasUserAgent = false;
  143. for (var i = 0, len = agents.length; i < len; i++) {
  144. var cls = agents[i];
  145. if (cls in goog.userAgent) {
  146. hasUserAgent = true;
  147. if (goog.userAgent[cls]) {
  148. return true;
  149. }
  150. }
  151. }
  152. // If we got here, there was a user agent listed but we didn't match it.
  153. return !hasUserAgent;
  154. };
  155. /**
  156. * Map function that converts end tags to a specific object.
  157. * @param {Node} node The node to map.
  158. * @param {undefined} ignore Always undefined.
  159. * @param {!goog.iter.Iterator<Node>} iterator The iterator.
  160. * @return {Node} The resulting iteration item.
  161. * @private
  162. */
  163. goog.testing.dom.endTagMap_ = function(node, ignore, iterator) {
  164. return iterator.isEndTag() ? goog.testing.dom.END_TAG_MARKER_ : node;
  165. };
  166. /**
  167. * Check if the given node is important. A node is important if it is a
  168. * non-empty text node, a non-annotated element, or an element annotated to
  169. * match on this user agent.
  170. * @param {Node} node The node to test.
  171. * @return {boolean} Whether this node should be included for iteration.
  172. * @private
  173. */
  174. goog.testing.dom.nodeFilter_ = function(node) {
  175. if (node.nodeType == goog.dom.NodeType.TEXT) {
  176. // If a node is part of a string of text nodes and it has spaces in it,
  177. // we allow it since it's going to affect the merging of nodes done below.
  178. if (goog.string.isBreakingWhitespace(node.nodeValue) &&
  179. (!node.previousSibling ||
  180. node.previousSibling.nodeType != goog.dom.NodeType.TEXT) &&
  181. (!node.nextSibling ||
  182. node.nextSibling.nodeType != goog.dom.NodeType.TEXT)) {
  183. return false;
  184. }
  185. // Allow optional text to be specified as [[BROWSER1 BROWSER2]]Text
  186. var match = node.nodeValue.match(/^\[\[(.+)\]\]/);
  187. if (match) {
  188. return goog.testing.dom.checkUserAgents_(match[1]);
  189. }
  190. } else if (node.className && goog.isString(node.className)) {
  191. return goog.testing.dom.checkUserAgents_(node.className);
  192. }
  193. return true;
  194. };
  195. /**
  196. * Determines the text to match from the given node, removing browser
  197. * specification strings.
  198. * @param {Node} node The node expected to match.
  199. * @return {string} The text, stripped of browser specification strings.
  200. * @private
  201. */
  202. goog.testing.dom.getExpectedText_ = function(node) {
  203. // Strip off the browser specifications.
  204. return node.nodeValue.match(/^(\[\[.+\]\])?([\s\S]*)/)[2];
  205. };
  206. /**
  207. * Describes the given node.
  208. * @param {Node} node The node to describe.
  209. * @return {string} A description of the node.
  210. * @private
  211. */
  212. goog.testing.dom.describeNode_ = function(node) {
  213. if (node.nodeType == goog.dom.NodeType.TEXT) {
  214. return '[Text: ' + node.nodeValue + ']';
  215. } else {
  216. return '<' + node.tagName + (node.id ? ' #' + node.id : '') + ' .../>';
  217. }
  218. };
  219. /**
  220. * Assert that the html in {@code actual} is substantially similar to
  221. * htmlPattern. This method tests for the same set of styles, for the same
  222. * order of nodes, and the presence of attributes. Breaking whitespace nodes
  223. * are ignored. Elements can be
  224. * annotated with classnames corresponding to keys in goog.userAgent and will be
  225. * expected to show up in that user agent and expected not to show up in
  226. * others.
  227. * @param {string} htmlPattern The pattern to match.
  228. * @param {!Node} actual The element to check: its contents are matched
  229. * against the HTML pattern.
  230. * @param {boolean=} opt_strictAttributes If false, attributes that appear in
  231. * htmlPattern must be in actual, but actual can have attributes not
  232. * present in htmlPattern. If true, htmlPattern and actual must have the
  233. * same set of attributes. Default is false.
  234. */
  235. goog.testing.dom.assertHtmlContentsMatch = function(
  236. htmlPattern, actual, opt_strictAttributes) {
  237. var div = goog.dom.createDom(goog.dom.TagName.DIV);
  238. div.innerHTML = htmlPattern;
  239. var errorSuffix =
  240. '\nExpected\n' + div.innerHTML + '\nActual\n' + actual.innerHTML;
  241. var actualIt = goog.iter.filter(
  242. goog.iter.map(
  243. new goog.dom.TagIterator(actual), goog.testing.dom.endTagMap_),
  244. goog.testing.dom.nodeFilter_);
  245. var expectedIt = goog.iter.filter(
  246. new goog.dom.NodeIterator(div), goog.testing.dom.nodeFilter_);
  247. var actualNode;
  248. var preIterated = false;
  249. var advanceActualNode = function() {
  250. // If the iterator has already been advanced, don't advance it again.
  251. if (!preIterated) {
  252. actualNode = goog.iter.nextOrValue(actualIt, null);
  253. }
  254. preIterated = false;
  255. // Advance the iterator so long as it is return end tags.
  256. while (actualNode == goog.testing.dom.END_TAG_MARKER_) {
  257. actualNode = goog.iter.nextOrValue(actualIt, null);
  258. }
  259. };
  260. // HACK(brenneman): IE has unique ideas about whitespace handling when setting
  261. // innerHTML. This results in elision of leading whitespace in the expected
  262. // nodes where doing so doesn't affect visible rendering. As a workaround, we
  263. // remove the leading whitespace in the actual nodes where necessary.
  264. //
  265. // The collapsible variable tracks whether we should collapse the whitespace
  266. // in the next Text node we encounter.
  267. var IE_TEXT_COLLAPSE =
  268. goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9');
  269. var collapsible = true;
  270. var number = 0;
  271. goog.iter.forEach(expectedIt, function(expectedNode) {
  272. advanceActualNode();
  273. assertNotNull(
  274. 'Finished actual HTML before finishing expected HTML at ' +
  275. 'node number ' + number + ': ' +
  276. goog.testing.dom.describeNode_(expectedNode) + errorSuffix,
  277. actualNode);
  278. // Do no processing for expectedNode == div.
  279. if (expectedNode == div) {
  280. return;
  281. }
  282. assertEquals(
  283. 'Should have the same node type, got ' +
  284. goog.testing.dom.describeNode_(actualNode) + ' but expected ' +
  285. goog.testing.dom.describeNode_(expectedNode) + '.' + errorSuffix,
  286. expectedNode.nodeType, actualNode.nodeType);
  287. if (expectedNode.nodeType == goog.dom.NodeType.ELEMENT) {
  288. var expectedElem = goog.asserts.assertElement(expectedNode);
  289. var actualElem = goog.asserts.assertElement(actualNode);
  290. assertEquals(
  291. 'Tag names should match' + errorSuffix, expectedElem.tagName,
  292. actualElem.tagName);
  293. assertObjectEquals(
  294. 'Should have same styles' + errorSuffix,
  295. goog.style.parseStyleAttribute(expectedElem.style.cssText),
  296. goog.style.parseStyleAttribute(actualElem.style.cssText));
  297. goog.testing.dom.assertAttributesEqual_(
  298. errorSuffix, expectedElem, actualElem, !!opt_strictAttributes);
  299. if (IE_TEXT_COLLAPSE &&
  300. goog.style.getCascadedStyle(actualElem, 'display') != 'inline') {
  301. // Text may be collapsed after any non-inline element.
  302. collapsible = true;
  303. }
  304. } else {
  305. // Concatenate text nodes until we reach a non text node.
  306. var actualText = actualNode.nodeValue;
  307. preIterated = true;
  308. while ((actualNode = goog.iter.nextOrValue(actualIt, null)) &&
  309. actualNode.nodeType == goog.dom.NodeType.TEXT) {
  310. actualText += actualNode.nodeValue;
  311. }
  312. if (IE_TEXT_COLLAPSE) {
  313. // Collapse the leading whitespace, unless the string consists entirely
  314. // of whitespace.
  315. if (collapsible && !goog.string.isEmptyOrWhitespace(actualText)) {
  316. actualText = goog.string.trimLeft(actualText);
  317. }
  318. // Prepare to collapse whitespace in the next Text node if this one does
  319. // not end in a whitespace character.
  320. collapsible = /\s$/.test(actualText);
  321. }
  322. var expectedText = goog.testing.dom.getExpectedText_(expectedNode);
  323. if ((actualText && !goog.string.isBreakingWhitespace(actualText)) ||
  324. (expectedText && !goog.string.isBreakingWhitespace(expectedText))) {
  325. var normalizedActual = actualText.replace(/\s+/g, ' ');
  326. var normalizedExpected = expectedText.replace(/\s+/g, ' ');
  327. assertEquals(
  328. 'Text should match' + errorSuffix, normalizedExpected,
  329. normalizedActual);
  330. }
  331. }
  332. number++;
  333. });
  334. advanceActualNode();
  335. assertNull(
  336. 'Finished expected HTML before finishing actual HTML' + errorSuffix,
  337. goog.iter.nextOrValue(actualIt, null));
  338. };
  339. /**
  340. * Assert that the html in {@code actual} is substantially similar to
  341. * htmlPattern. This method tests for the same set of styles, and for the same
  342. * order of nodes. Breaking whitespace nodes are ignored. Elements can be
  343. * annotated with classnames corresponding to keys in goog.userAgent and will be
  344. * expected to show up in that user agent and expected not to show up in
  345. * others.
  346. * @param {string} htmlPattern The pattern to match.
  347. * @param {string} actual The html to check.
  348. * @param {boolean=} opt_strictAttributes If false, attributes that appear in
  349. * htmlPattern must be in actual, but actual can have attributes not
  350. * present in htmlPattern. If true, htmlPattern and actual must have the
  351. * same set of attributes. Default is false.
  352. */
  353. goog.testing.dom.assertHtmlMatches = function(
  354. htmlPattern, actual, opt_strictAttributes) {
  355. var div = goog.dom.createDom(goog.dom.TagName.DIV);
  356. div.innerHTML = actual;
  357. goog.testing.dom.assertHtmlContentsMatch(
  358. htmlPattern, div, opt_strictAttributes);
  359. };
  360. /**
  361. * Finds the first text node descendant of root with the given content. Note
  362. * that this operates on a text node level, so if text nodes get split this
  363. * may not match the user visible text. Using normalize() may help here.
  364. * @param {string|RegExp} textOrRegexp The text to find, or a regular
  365. * expression to find a match of.
  366. * @param {Element} root The element to search in.
  367. * @return {?Node} The first text node that matches, or null if none is found.
  368. */
  369. goog.testing.dom.findTextNode = function(textOrRegexp, root) {
  370. var it = new goog.dom.NodeIterator(root);
  371. var ret = goog.iter.nextOrValue(goog.iter.filter(it, function(node) {
  372. if (node.nodeType == goog.dom.NodeType.TEXT) {
  373. if (goog.isString(textOrRegexp)) {
  374. return node.nodeValue == textOrRegexp;
  375. } else {
  376. return !!node.nodeValue.match(textOrRegexp);
  377. }
  378. } else {
  379. return false;
  380. }
  381. }), null);
  382. return ret;
  383. };
  384. /**
  385. * Assert the end points of a range.
  386. *
  387. * Notice that "Are two ranges visually identical?" and "Do two ranges have
  388. * the same endpoint?" are independent questions. Two visually identical ranges
  389. * may have different endpoints. And two ranges with the same endpoints may
  390. * be visually different.
  391. *
  392. * @param {Node} start The expected start node.
  393. * @param {number} startOffset The expected start offset.
  394. * @param {Node} end The expected end node.
  395. * @param {number} endOffset The expected end offset.
  396. * @param {goog.dom.AbstractRange} range The actual range.
  397. */
  398. goog.testing.dom.assertRangeEquals = function(
  399. start, startOffset, end, endOffset, range) {
  400. assertEquals('Unexpected start node', start, range.getStartNode());
  401. assertEquals('Unexpected end node', end, range.getEndNode());
  402. assertEquals('Unexpected start offset', startOffset, range.getStartOffset());
  403. assertEquals('Unexpected end offset', endOffset, range.getEndOffset());
  404. };
  405. /**
  406. * Gets the value of a DOM attribute in deterministic way.
  407. * @param {!Node} node A node.
  408. * @param {string} name Attribute name.
  409. * @return {*} Attribute value.
  410. * @private
  411. */
  412. goog.testing.dom.getAttributeValue_ = function(node, name) {
  413. // These hacks avoid nondetermistic results in the following cases:
  414. // IE7: goog.dom.createElement(goog.dom.TagName.INPUT).height returns
  415. // a random number.
  416. // FF3: getAttribute('disabled') returns different value for <div disabled="">
  417. // and <div disabled="disabled">
  418. // WebKit: Two radio buttons with the same name can't be checked at the same
  419. // time, even if only one of them is in the document.
  420. if (goog.userAgent.WEBKIT && node.tagName == goog.dom.TagName.INPUT &&
  421. node['type'] == goog.dom.InputType.RADIO && name == 'checked') {
  422. return false;
  423. }
  424. return goog.isDef(node[name]) &&
  425. typeof node.getAttribute(name) != typeof node[name] ?
  426. node[name] :
  427. node.getAttribute(name);
  428. };
  429. /**
  430. * Assert that the attributes of two Nodes are the same (ignoring any
  431. * instances of the style attribute).
  432. * @param {string} errorSuffix String to add to end of error messages.
  433. * @param {!Element} expectedElem The element whose attributes we are expecting.
  434. * @param {!Element} actualElem The element with the actual attributes.
  435. * @param {boolean} strictAttributes If false, attributes that appear in
  436. * expectedNode must also be in actualNode, but actualNode can have
  437. * attributes not present in expectedNode. If true, expectedNode and
  438. * actualNode must have the same set of attributes.
  439. * @private
  440. */
  441. goog.testing.dom.assertAttributesEqual_ = function(
  442. errorSuffix, expectedElem, actualElem, strictAttributes) {
  443. if (strictAttributes) {
  444. goog.testing.dom.compareClassAttribute_(expectedElem, actualElem);
  445. }
  446. var expectedAttributes = expectedElem.attributes;
  447. var actualAttributes = actualElem.attributes;
  448. for (var i = 0, len = expectedAttributes.length; i < len; i++) {
  449. var expectedName = expectedAttributes[i].name;
  450. var expectedValue =
  451. goog.testing.dom.getAttributeValue_(expectedElem, expectedName);
  452. var actualAttribute = actualAttributes[expectedName];
  453. var actualValue =
  454. goog.testing.dom.getAttributeValue_(actualElem, expectedName);
  455. // IE enumerates attribute names in the expected node that are not present,
  456. // causing an undefined actualAttribute.
  457. if (!expectedValue && !actualValue) {
  458. continue;
  459. }
  460. if (expectedName == 'id' && goog.userAgent.IE) {
  461. goog.testing.dom.compareIdAttributeForIe_(
  462. /** @type {string} */ (expectedValue), actualAttribute,
  463. strictAttributes, errorSuffix);
  464. continue;
  465. }
  466. if (goog.testing.dom.ignoreAttribute_(expectedName)) {
  467. continue;
  468. }
  469. assertNotUndefined(
  470. 'Expected to find attribute with name ' + expectedName +
  471. ', in element ' + goog.testing.dom.describeNode_(actualElem) +
  472. errorSuffix,
  473. actualAttribute);
  474. assertEquals(
  475. 'Expected attribute ' + expectedName + ' has a different value ' +
  476. errorSuffix,
  477. String(expectedValue), String(
  478. goog.testing.dom.getAttributeValue_(
  479. actualElem, actualAttribute.name)));
  480. }
  481. if (strictAttributes) {
  482. for (i = 0; i < actualAttributes.length; i++) {
  483. var actualName = actualAttributes[i].name;
  484. var actualAttribute = actualAttributes.getNamedItem(actualName);
  485. if (!actualAttribute || goog.testing.dom.ignoreAttribute_(actualName)) {
  486. continue;
  487. }
  488. assertNotUndefined(
  489. 'Unexpected attribute with name ' + actualName + ' in element ' +
  490. goog.testing.dom.describeNode_(actualElem) + errorSuffix,
  491. expectedAttributes[actualName]);
  492. }
  493. }
  494. };
  495. /**
  496. * Assert the class attribute of actualElem is the same as the one in
  497. * expectedElem, ignoring classes that are useragents.
  498. * @param {!Element} expectedElem The DOM element whose class we expect.
  499. * @param {!Element} actualElem The DOM element with the actual class.
  500. * @private
  501. */
  502. goog.testing.dom.compareClassAttribute_ = function(expectedElem, actualElem) {
  503. var classes = goog.dom.classlist.get(expectedElem);
  504. var expectedClasses = [];
  505. for (var i = 0, len = classes.length; i < len; i++) {
  506. if (!(classes[i] in goog.userAgent)) {
  507. expectedClasses.push(classes[i]);
  508. }
  509. }
  510. expectedClasses.sort();
  511. var actualClasses = goog.array.toArray(goog.dom.classlist.get(actualElem));
  512. actualClasses.sort();
  513. assertArrayEquals(
  514. 'Expected class was: ' + expectedClasses.join(' ') +
  515. ', but actual class was: ' + actualElem.className + ' in node ' +
  516. goog.testing.dom.describeNode_(actualElem),
  517. expectedClasses, actualClasses);
  518. };
  519. /**
  520. * Set of attributes IE adds to elements randomly.
  521. * @type {Object}
  522. * @private
  523. */
  524. goog.testing.dom.BAD_IE_ATTRIBUTES_ = goog.object.createSet(
  525. 'methods', 'CHECKED', 'dataFld', 'dataFormatAs', 'dataSrc');
  526. /**
  527. * Whether to ignore the attribute.
  528. * @param {string} name Name of the attribute.
  529. * @return {boolean} True if the attribute should be ignored.
  530. * @private
  531. */
  532. goog.testing.dom.ignoreAttribute_ = function(name) {
  533. if (name == 'style' || name == 'class') {
  534. return true;
  535. }
  536. return goog.userAgent.IE && goog.testing.dom.BAD_IE_ATTRIBUTES_[name];
  537. };
  538. /**
  539. * Compare id attributes for IE. In IE, if an element lacks an id attribute
  540. * in the original HTML, the element object will still have such an attribute,
  541. * but its value will be the empty string.
  542. * @param {string} expectedValue The expected value of the id attribute.
  543. * @param {Attr} actualAttribute The actual id attribute.
  544. * @param {boolean} strictAttributes Whether strict attribute checking should be
  545. * done.
  546. * @param {string} errorSuffix String to append to error messages.
  547. * @private
  548. */
  549. goog.testing.dom.compareIdAttributeForIe_ = function(
  550. expectedValue, actualAttribute, strictAttributes, errorSuffix) {
  551. if (expectedValue === '') {
  552. if (strictAttributes) {
  553. assertTrue(
  554. 'Unexpected attribute with name id in element ' + errorSuffix,
  555. actualAttribute.value == '');
  556. }
  557. } else {
  558. assertNotUndefined(
  559. 'Expected to find attribute with name id, in element ' + errorSuffix,
  560. actualAttribute);
  561. assertNotEquals(
  562. 'Expected to find attribute with name id, in element ' + errorSuffix,
  563. '', actualAttribute.value);
  564. assertEquals(
  565. 'Expected attribute has a different value ' + errorSuffix,
  566. expectedValue, actualAttribute.value);
  567. }
  568. };