diff --git a/src/js/_enqueues/wp/emoji.js b/src/js/_enqueues/wp/emoji.js index a1f0f4face234..9ec1a788a95b0 100644 --- a/src/js/_enqueues/wp/emoji.js +++ b/src/js/_enqueues/wp/emoji.js @@ -110,7 +110,9 @@ // Emoji in a text node are replaced by parsing the element which contains it. const node = addedNode instanceof Text ? addedNode.parentElement : addedNode; - if ( node instanceof HTMLElement && test( node.textContent ) ) { + if ( + node instanceof HTMLElement && test( node.textContent ) && ! isExcludedFromParsing( node ) + ) { parse( node ); } } @@ -123,6 +125,39 @@ parse( document.body ); } + /** + * Checks whether a node, or one of its ancestors, is excluded from + * emoji parsing. + * + * The doNotParse() callback passed to Twemoji only excludes descendants + * of the node it's asked to parse, so a node that is itself excluded + * (or nested inside an excluded ancestor) has to be checked here, + * before it's ever handed to parse(). + * + * @since 7.2.0 + * @private + * + * @param {HTMLElement|null} node The node to check. + * + * @return {boolean} Whether the node or an ancestor has the + * wp-exclude-emoji class. + */ + function isExcludedFromParsing( node ) { + while ( node ) { + if ( + node.className && + typeof node.className === 'string' && + node.className.indexOf( 'wp-exclude-emoji' ) !== -1 + ) { + return true; + } + + node = node.parentElement; + } + + return false; + } + /** * Tests if a text string contains emoji characters. * diff --git a/tests/qunit/wp-includes/js/emoji.html b/tests/qunit/wp-includes/js/emoji.html new file mode 100644 index 0000000000000..049a6468c2f5a --- /dev/null +++ b/tests/qunit/wp-includes/js/emoji.html @@ -0,0 +1,31 @@ + + + + wp.emoji QUnit Test Suite + + + + + + + +
+
+ + + + + + + diff --git a/tests/qunit/wp-includes/js/emoji.js b/tests/qunit/wp-includes/js/emoji.js new file mode 100644 index 0000000000000..597071079ee8c --- /dev/null +++ b/tests/qunit/wp-includes/js/emoji.js @@ -0,0 +1,52 @@ +( function( QUnit ) { + var FLAG = '🇰🇷'; // Flag: South Korea. + + QUnit.module( 'wp.emoji' ); + + function assertNotParsed( assert, container, done ) { + window.setTimeout( function() { + assert.strictEqual( container.querySelector( 'img.emoji' ), null, 'the excluded element should not contain an emoji image' ); + container.remove(); + done(); + }, 50 ); + } + + QUnit.test( 'does not parse an excluded element appended whole after load', function( assert ) { + var done = assert.async(), + span = document.createElement( 'span' ); + + span.className = 'wp-exclude-emoji'; + span.textContent = FLAG; + document.body.appendChild( span ); + + assertNotParsed( assert, span, done ); + } ); + + QUnit.test( 'does not parse an excluded element whose text is replaced after load', function( assert ) { + var done = assert.async(), + span = document.createElement( 'span' ); + + span.className = 'wp-exclude-emoji'; + document.body.appendChild( span ); + + window.setTimeout( function() { + span.textContent = FLAG; + assertNotParsed( assert, span, done ); + }, 50 ); + } ); + + QUnit.test( 'does not parse text appended inside an already-excluded ancestor', function( assert ) { + var done = assert.async(), + wrapper = document.createElement( 'span' ), + child = document.createElement( 'em' ); + + wrapper.className = 'wp-exclude-emoji'; + wrapper.appendChild( child ); + document.body.appendChild( wrapper ); + + window.setTimeout( function() { + child.textContent = FLAG; + assertNotParsed( assert, wrapper, done ); + }, 50 ); + } ); +} )( window.QUnit );