Implement htmlEqual QUnit helper (QUnit.assert.htmlEqual)
authorMatthew Flaschen <mflaschen@wikimedia.org>
Wed, 6 Mar 2013 00:56:15 +0000 (16:56 -0800)
committerMatthew Flaschen <mflaschen@wikimedia.org>
Wed, 13 Mar 2013 07:35:02 +0000 (03:35 -0400)
Bug: 45526
Change-Id: I4d0ce6226f4ef73d659ba0635f2b2f49d8b0e9b5

RELEASE-NOTES-1.21
resources/Resources.php
tests/qunit/data/testrunner.js

index 5864069..f8c7aed 100644 (file)
@@ -117,6 +117,9 @@ production.
 * Added 'HistoryRevisionTools' and 'DiffRevisionTools' hooks.
 * (bug 33186) Add image rotation api "imagerotate"
 * (bug 34040) Add "User rights management" link on user page toolbox.
+* (bug 45526) Add QUnit assertion helper "QUnit.assert.htmlEqual" for asserting
+  structual equality of HTML (ignoring insignificant differences like
+  quotmarks, order and whitespace in the attribute list).
 
 === Bug fixes in 1.21 ===
 * (bug 40353) SpecialDoubleRedirect should support interwiki redirects.
index 10689cd..1a11cd4 100644 (file)
@@ -957,6 +957,7 @@ return array(
        'mediawiki.tests.qunit.testrunner' => array(
                'scripts' => 'tests/qunit/data/testrunner.js',
                'dependencies' => array(
+                       'jquery.getAttrs',
                        'jquery.qunit',
                        'jquery.qunit.completenessTest',
                        'mediawiki.page.startup',
index 77db213..62dd81a 100644 (file)
@@ -5,7 +5,9 @@
 
        var mwTestIgnore, mwTester,
                addons,
-               envExecCount;
+               envExecCount,
+               ELEMENT_NODE = 1,
+               TEXT_NODE = 3;
 
        /**
         * Add bogus to url to prevent IE crazy caching
                return $.when.apply( $, altPromises );
        };
 
+       /**
+        * Recursively convert a node to a plain object representing its structure.
+        * Only considers attributes and contents (elements and text nodes).
+        * Attribute values are compared strictly and not normalised.
+        *
+        * @param {Node} node
+        * @return {Object|string} Plain JavaScript value representing the node.
+        */
+       function getDomStructure( node ) {
+               var $node, children, processedChildren, i, len, el;
+               $node = $( node );
+               if ( node.nodeType === ELEMENT_NODE ) {
+                       children = $node.contents();
+                       processedChildren = [];
+                       for ( i = 0, len = children.length; i < len; i++ ) {
+                               el = children[i];
+                               if ( el.nodeType === ELEMENT_NODE || el.nodeType === TEXT_NODE ) {
+                                       processedChildren.push( getDomStructure( el ) );
+                               }
+                       }
+
+                       return {
+                               tagName: node.tagName,
+                               attributes: $node.getAttrs(),
+                               contents: processedChildren
+                       };
+               } else {
+                       // Should be text node
+                       return $node.text();
+               }
+       }
+
+       /**
+        * Gets structure of node for this HTML.
+        *
+        * @param {string} html HTML markup for one or more nodes.
+        */
+       function getHtmlStructure( html ) {
+               var el = $( '<div>' ).append( html )[0];
+               return getDomStructure( el );
+       }
+
        /**
         * Add-on assertion helpers
         */
                // Expect numerical value greater than or equal to X
                gtOrEq: function ( actual, expected, message ) {
                        QUnit.push( actual >= expected, actual, 'greater than or equal to ' + expected, message );
+               },
+
+               /**
+                * Asserts that two HTML strings are structurally equivalent.
+                *
+                * @param {string} actualHtml Actual HTML markup.
+                * @param {string} expectedHtml Expected HTML markup
+                * @param {string} message Assertion message.
+                */
+               htmlEqual: function ( actualHtml, expectedHtml, message ) {
+                       var actual = getHtmlStructure( actualHtml ),
+                               expected = getHtmlStructure( expectedHtml );
+
+                       QUnit.push(
+                               QUnit.equiv(
+                                       actual,
+                                       expected
+                               ),
+                               actual,
+                               expected,
+                               message
+                       );
+               },
+
+               /**
+                * Asserts that two HTML strings are not structurally equivalent.
+                *
+                * @param {string} actualHtml Actual HTML markup.
+                * @param {string} expectedHtml Expected HTML markup.
+                * @param {string} message Assertion message.
+                */
+               notHtmlEqual: function ( actualHtml, expectedHtml, message ) {
+                       var actual = getHtmlStructure( actualHtml ),
+                               expected = getHtmlStructure( expectedHtml );
+
+                       QUnit.push(
+                               !QUnit.equiv(
+                                       actual,
+                                       expected
+                               ),
+                               actual,
+                               expected,
+                               message
+                       );
                }
        };
 
                assert.equal( mw.messages.get( 'testMsg' ), 'Foo.', 'messages object restored and re-applied after test()' );
        } );
 
+       QUnit.test( 'htmlEqual', 8, function ( assert ) {
+               assert.htmlEqual(
+                       '<div><p class="some classes" data-length="10">Child paragraph with <a href="http://example.com">A link</a></p>Regular text<span>A span</span></div>',
+                       '<div><p data-length=\'10\'  class=\'some classes\'>Child paragraph with <a href=\'http://example.com\' >A link</a></p>Regular text<span>A span</span></div>',
+                       'Attribute order, spacing and quotation marks (equal)'
+               );
+
+               assert.notHtmlEqual(
+                       '<div><p class="some classes" data-length="10">Child paragraph with <a href="http://example.com">A link</a></p>Regular text<span>A span</span></div>',
+                       '<div><p data-length=\'10\'  class=\'some more classes\'>Child paragraph with <a href=\'http://example.com\' >A link</a></p>Regular text<span>A span</span></div>',
+                       'Attribute order, spacing and quotation marks (not equal)'
+               );
+
+               assert.htmlEqual(
+                       '<label for="firstname" accesskey="f" class="important">First</label><input id="firstname" /><label for="lastname" accesskey="l" class="minor">Last</label><input id="lastname" />',
+                       '<label for="firstname" accesskey="f" class="important">First</label><input id="firstname" /><label for="lastname" accesskey="l" class="minor">Last</label><input id="lastname" />',
+                       'Multiple root nodes (equal)'
+               );
+
+               assert.notHtmlEqual(
+                       '<label for="firstname" accesskey="f" class="important">First</label><input id="firstname" /><label for="lastname" accesskey="l" class="minor">Last</label><input id="lastname" />',
+                       '<label for="firstname" accesskey="f" class="important">First</label><input id="firstname" /><label for="lastname" accesskey="l" class="important" >Last</label><input id="lastname" />',
+                       'Multiple root nodes (not equal, last label node is different)'
+               );
+
+               assert.htmlEqual(
+                       'fo&quot;o<br/>b&gt;ar',
+                       'fo"o<br/>b>ar',
+                       'Extra escaping is equal'
+               );
+               assert.notHtmlEqual(
+                       'foo&lt;br/&gt;bar',
+                       'foo<br/>bar',
+                       'Text escaping (not equal)'
+               );
+
+               assert.htmlEqual(
+                       'foo<a href="http://example.com">example</a>bar',
+                       'foo<a href="http://example.com">example</a>bar',
+                       'Outer text nodes are compared (equal)'
+               );
+
+               assert.notHtmlEqual(
+                       'foo<a href="http://example.com">example</a>bar',
+                       'foo<a href="http://example.com">example</a>quux',
+                       'Outer text nodes are compared (last text node different)'
+               );
+
+       } );
+
        QUnit.module( 'mediawiki.tests.qunit.testrunner-after', QUnit.newMwEnvironment() );
 
        QUnit.test( 'Teardown', 3, function ( assert ) {